From cf597761491d8b50d95e622d1d3cb83af9217b0c Mon Sep 17 00:00:00 2001 From: Coung Ngo Date: Fri, 23 Apr 2021 17:13:55 +0100 Subject: [PATCH 1/5] Add iteration filter token to issues list page refactor Added behind `vue_issues_list` feature flag defaulted to off, as part of an ongoing refactor from Haml. https://gitlab.com/gitlab-org/gitlab/-/issues/322755 --- .../components/issues_list_app.vue | 23 +++- .../javascripts/issues_list/constants.js | 10 ++ app/assets/javascripts/issues_list/index.js | 2 + .../filtered_search_bar/constants.js | 16 +-- .../tokens/iteration_token.vue | 110 ++++++++++++++++++ ee/app/helpers/ee/issues_helper.rb | 8 +- ee/spec/helpers/ee/issues_helper_spec.rb | 3 +- spec/frontend/issues_list/mock_data.js | 8 ++ .../filtered_search_bar/mock_data.js | 10 ++ .../tokens/iteration_token_spec.js | 78 +++++++++++++ 10 files changed, 258 insertions(+), 10 deletions(-) create mode 100644 app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue create mode 100644 spec/frontend/vue_shared/components/filtered_search_bar/tokens/iteration_token_spec.js diff --git a/app/assets/javascripts/issues_list/components/issues_list_app.vue b/app/assets/javascripts/issues_list/components/issues_list_app.vue index 3ccf982ef019da..935a4853c296ef 100644 --- a/app/assets/javascripts/issues_list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues_list/components/issues_list_app.vue @@ -36,6 +36,7 @@ import { convertObjectPropsToCamelCase, getParameterByName } from '~/lib/utils/c import { __ } from '~/locale'; import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue'; import EmojiToken from '~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue'; +import IterationToken from '~/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue'; import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue'; import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'; import eventHub from '../eventhub'; @@ -103,6 +104,9 @@ export default { newIssuePath: { default: '', }, + projectIterationsPath: { + default: '', + }, projectLabelsPath: { default: '', }, @@ -155,7 +159,7 @@ export default { return convertToSearchQuery(this.filterTokens) || undefined; }, searchTokens() { - return [ + const tokens = [ { type: 'author_username', title: __('Author'), @@ -216,6 +220,20 @@ export default { ], }, ]; + + if (this.projectIterationsPath) { + tokens.push({ + type: 'iteration', + title: __('Iteration'), + icon: 'iteration', + token: IterationToken, + unique: true, + defaultIterations: [], + fetchIterations: this.fetchIterations, + }); + } + + return tokens; }, showPaginationControls() { return this.issues.length > 0; @@ -273,6 +291,9 @@ export default { fetchMilestones(search) { return this.fetchWithCache(this.projectMilestonesPath, 'milestones', 'title', search, true); }, + fetchIterations(search) { + return axios.get(this.projectIterationsPath, { params: { search } }); + }, fetchUsers(search) { return axios.get(this.autocompleteUsersPath, { params: { search } }); }, diff --git a/app/assets/javascripts/issues_list/constants.js b/app/assets/javascripts/issues_list/constants.js index 60a211ec8c042c..441a4bf9891d75 100644 --- a/app/assets/javascripts/issues_list/constants.js +++ b/app/assets/javascripts/issues_list/constants.js @@ -334,4 +334,14 @@ export const filters = { [OPERATOR_IS]: 'confidential', }, }, + iteration: { + apiParam: { + [OPERATOR_IS]: 'iteration_title', + [OPERATOR_IS_NOT]: 'not[iteration_title]', + }, + urlParam: { + [OPERATOR_IS]: 'iteration_title', + [OPERATOR_IS_NOT]: 'not[iteration_title]', + }, + }, }; diff --git a/app/assets/javascripts/issues_list/index.js b/app/assets/javascripts/issues_list/index.js index 06c50a02ada64f..0318c2c24846bc 100644 --- a/app/assets/javascripts/issues_list/index.js +++ b/app/assets/javascripts/issues_list/index.js @@ -98,6 +98,7 @@ export function initIssuesListApp() { maxAttachmentSize, newIssuePath, projectImportJiraPath, + projectIterationsPath, projectLabelsPath, projectMilestonesPath, projectPath, @@ -128,6 +129,7 @@ export function initIssuesListApp() { issuesPath, jiraIntegrationPath, newIssuePath, + projectIterationsPath, projectLabelsPath, projectMilestonesPath, projectPath, diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js index 3d8afd162cbebf..e2868879425195 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js @@ -1,18 +1,16 @@ /* eslint-disable @gitlab/require-i18n-strings */ import { __ } from '~/locale'; +export const DEBOUNCE_DELAY = 200; + const DEFAULT_LABEL_NO_LABEL = { value: 'No label', text: __('No label') }; export const DEFAULT_LABEL_NONE = { value: 'None', text: __('None') }; export const DEFAULT_LABEL_ANY = { value: 'Any', text: __('Any') }; +export const DEFAULT_LABEL_CURRENT = { value: 'Current', text: __('Current') }; -export const DEFAULT_LABELS = [DEFAULT_LABEL_NO_LABEL]; +export const DEFAULT_ITERATIONS = [DEFAULT_LABEL_NONE, DEFAULT_LABEL_ANY, DEFAULT_LABEL_CURRENT]; -export const DEBOUNCE_DELAY = 200; - -export const SortDirection = { - descending: 'descending', - ascending: 'ascending', -}; +export const DEFAULT_LABELS = [DEFAULT_LABEL_NO_LABEL]; export const DEFAULT_MILESTONES = [ DEFAULT_LABEL_NONE, @@ -21,4 +19,8 @@ export const DEFAULT_MILESTONES = [ { value: 'Started', text: __('Started') }, ]; +export const SortDirection = { + descending: 'descending', + ascending: 'ascending', +}; /* eslint-enable @gitlab/require-i18n-strings */ diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue new file mode 100644 index 00000000000000..7b6a590279a889 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue @@ -0,0 +1,110 @@ + + + diff --git a/ee/app/helpers/ee/issues_helper.rb b/ee/app/helpers/ee/issues_helper.rb index 82ce527267db1b..4ef4b4618fde46 100644 --- a/ee/app/helpers/ee/issues_helper.rb +++ b/ee/app/helpers/ee/issues_helper.rb @@ -71,11 +71,17 @@ def issue_header_actions_data(project, issuable, current_user) override :issues_list_data def issues_list_data(project, current_user, finder) - super.merge!( + data = super.merge!( has_blocked_issues_feature: project.feature_available?(:blocked_issues).to_s, has_issuable_health_status_feature: project.feature_available?(:issuable_health_status).to_s, has_issue_weights_feature: project.feature_available?(:issue_weights).to_s ) + + if project.feature_available?(:iterations) + data[:project_iterations_path] = api_v4_projects_iterations_path(id: project.id) + end + + data end end end diff --git a/ee/spec/helpers/ee/issues_helper_spec.rb b/ee/spec/helpers/ee/issues_helper_spec.rb index 319de03fd262bf..e624b37c8c9967 100644 --- a/ee/spec/helpers/ee/issues_helper_spec.rb +++ b/ee/spec/helpers/ee/issues_helper_spec.rb @@ -137,7 +137,8 @@ expected = { has_blocked_issues_feature: 'true', has_issuable_health_status_feature: 'true', - has_issue_weights_feature: 'true' + has_issue_weights_feature: 'true', + project_iterations_path: api_v4_projects_iterations_path(id: project.id) } expect(helper.issues_list_data(project, current_user, finder)).to include(expected) diff --git a/spec/frontend/issues_list/mock_data.js b/spec/frontend/issues_list/mock_data.js index faeab0c244dd88..98a03667bd08c2 100644 --- a/spec/frontend/issues_list/mock_data.js +++ b/spec/frontend/issues_list/mock_data.js @@ -14,6 +14,8 @@ export const locationSearch = [ 'not[label_name][]=drama', 'my_reaction_emoji=thumbsup', 'confidential=no', + 'iteration_title=season:+%234', + 'not[iteration_title]=season:+%2320', ].join('&'); export const filteredTokens = [ @@ -29,6 +31,8 @@ export const filteredTokens = [ { type: 'labels', value: { data: 'drama', operator: OPERATOR_IS_NOT } }, { type: 'my_reaction_emoji', value: { data: 'thumbsup', operator: OPERATOR_IS } }, { type: 'confidential', value: { data: 'no', operator: OPERATOR_IS } }, + { type: 'iteration', value: { data: 'season: #4', operator: OPERATOR_IS } }, + { type: 'iteration', value: { data: 'season: #20', operator: OPERATOR_IS_NOT } }, { type: 'filtered-search-term', value: { data: 'find' } }, { type: 'filtered-search-term', value: { data: 'issues' } }, ]; @@ -44,6 +48,8 @@ export const apiParams = { 'not[labels]': 'live action,drama', my_reaction_emoji: 'thumbsup', confidential: 'no', + iteration_title: 'season: #4', + 'not[iteration_title]': 'season: #20', }; export const urlParams = { @@ -57,4 +63,6 @@ export const urlParams = { 'not[label_name][]': ['live action', 'drama'], my_reaction_emoji: ['thumbsup'], confidential: ['no'], + iteration_title: ['season: #4'], + 'not[iteration_title]': ['season: #20'], }; diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js index 46b7e49979e517..e5943dc20b31d0 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js @@ -5,6 +5,7 @@ import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/auth import BranchToken from '~/vue_shared/components/filtered_search_bar/tokens/branch_token.vue'; import EmojiToken from '~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue'; import EpicToken from '~/vue_shared/components/filtered_search_bar/tokens/epic_token.vue'; +import IterationToken from '~/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue'; import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue'; import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'; @@ -98,6 +99,15 @@ export const mockAuthorToken = { fetchAuthors: Api.projectUsers.bind(Api), }; +export const mockIterationToken = { + type: 'iteration', + icon: 'iteration', + title: 'Iteration', + unique: true, + token: IterationToken, + fetchIterations: () => Promise.resolve(), +}; + export const mockLabelToken = { type: 'label_name', icon: 'labels', diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/iteration_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/iteration_token_spec.js new file mode 100644 index 00000000000000..88e5e7fa983dca --- /dev/null +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/iteration_token_spec.js @@ -0,0 +1,78 @@ +import { GlFilteredSearchToken, GlFilteredSearchTokenSegment } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import createFlash from '~/flash'; +import IterationToken from '~/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue'; +import { mockIterationToken } from '../mock_data'; + +jest.mock('~/flash'); + +describe('IterationToken', () => { + const title = 'gitlab-org: #1'; + let wrapper; + + const createComponent = ({ config = mockIterationToken, value = { data: '' } } = {}) => + mount(IterationToken, { + propsData: { + config, + value, + }, + provide: { + portalName: 'fake target', + alignSuggestions: function fakeAlignSuggestions() {}, + suggestionsListClass: 'custom-class', + }, + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders iteration value', async () => { + wrapper = createComponent({ value: { data: title } }); + + await wrapper.vm.$nextTick(); + + const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + + expect(tokenSegments).toHaveLength(3); // `Iteration` `=` `gitlab-org: #1` + expect(tokenSegments.at(2).text()).toBe(title); + }); + + it('fetches initial values', () => { + const fetchIterationsSpy = jest.fn().mockResolvedValue(); + + wrapper = createComponent({ + config: { ...mockIterationToken, fetchIterations: fetchIterationsSpy }, + value: { data: title }, + }); + + expect(fetchIterationsSpy).toHaveBeenCalledWith(title); + }); + + it('fetches iterations on user input', () => { + const search = 'hello'; + const fetchIterationsSpy = jest.fn().mockResolvedValue(); + + wrapper = createComponent({ + config: { ...mockIterationToken, fetchIterations: fetchIterationsSpy }, + }); + + wrapper.findComponent(GlFilteredSearchToken).vm.$emit('input', { data: search }); + + expect(fetchIterationsSpy).toHaveBeenCalledWith(search); + }); + + it('renders error message when request fails', async () => { + const fetchIterationsSpy = jest.fn().mockRejectedValue(); + + wrapper = createComponent({ + config: { ...mockIterationToken, fetchIterations: fetchIterationsSpy }, + }); + + await wrapper.vm.$nextTick(); + + expect(createFlash).toHaveBeenCalledWith({ + message: 'There was a problem fetching iterations.', + }); + }); +}); -- GitLab From 51e2aff2a7d49a3051f65ca28d921ad2c7a8c963 Mon Sep 17 00:00:00 2001 From: Coung Ngo Date: Fri, 23 Apr 2021 18:31:13 +0100 Subject: [PATCH 2/5] Add weight token --- .../components/issues_list_app.vue | 14 +++++ .../javascripts/issues_list/constants.js | 10 ++++ .../tokens/weight_token.vue | 58 +++++++++++++++++++ locale/gitlab.pot | 3 + spec/frontend/issues_list/mock_data.js | 8 +++ .../filtered_search_bar/mock_data.js | 9 +++ .../tokens/iteration_token_spec.js | 2 +- .../tokens/weight_token_spec.js | 37 ++++++++++++ 8 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/weight_token.vue create mode 100644 spec/frontend/vue_shared/components/filtered_search_bar/tokens/weight_token_spec.js diff --git a/app/assets/javascripts/issues_list/components/issues_list_app.vue b/app/assets/javascripts/issues_list/components/issues_list_app.vue index 935a4853c296ef..560f691eb4e0b4 100644 --- a/app/assets/javascripts/issues_list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues_list/components/issues_list_app.vue @@ -39,6 +39,7 @@ import EmojiToken from '~/vue_shared/components/filtered_search_bar/tokens/emoji import IterationToken from '~/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue'; import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue'; import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'; +import WeightToken from '~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue'; import eventHub from '../eventhub'; import IssueCardTimeInfo from './issue_card_time_info.vue'; @@ -89,6 +90,9 @@ export default { hasIssues: { default: false, }, + hasIssueWeightsFeature: { + default: false, + }, initialEmail: { default: '', }, @@ -233,6 +237,16 @@ export default { }); } + if (this.hasIssueWeightsFeature) { + tokens.push({ + type: 'weight', + title: __('Weight'), + icon: 'weight', + token: WeightToken, + unique: true, + }); + } + return tokens; }, showPaginationControls() { diff --git a/app/assets/javascripts/issues_list/constants.js b/app/assets/javascripts/issues_list/constants.js index 441a4bf9891d75..38a0d953835830 100644 --- a/app/assets/javascripts/issues_list/constants.js +++ b/app/assets/javascripts/issues_list/constants.js @@ -344,4 +344,14 @@ export const filters = { [OPERATOR_IS_NOT]: 'not[iteration_title]', }, }, + weight: { + apiParam: { + [OPERATOR_IS]: 'weight', + [OPERATOR_IS_NOT]: 'not[weight]', + }, + urlParam: { + [OPERATOR_IS]: 'weight', + [OPERATOR_IS_NOT]: 'not[weight]', + }, + }, }; diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/weight_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/weight_token.vue new file mode 100644 index 00000000000000..cfad79b9afaacf --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/weight_token.vue @@ -0,0 +1,58 @@ + + + diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f350f6ece5d215..1a263fea5bcabe 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -32168,6 +32168,9 @@ msgstr "" msgid "There was a problem fetching groups." msgstr "" +msgid "There was a problem fetching iterations." +msgstr "" + msgid "There was a problem fetching labels." msgstr "" diff --git a/spec/frontend/issues_list/mock_data.js b/spec/frontend/issues_list/mock_data.js index 98a03667bd08c2..f75c3d8bcb9c4d 100644 --- a/spec/frontend/issues_list/mock_data.js +++ b/spec/frontend/issues_list/mock_data.js @@ -16,6 +16,8 @@ export const locationSearch = [ 'confidential=no', 'iteration_title=season:+%234', 'not[iteration_title]=season:+%2320', + 'weight=1', + 'not[weight]=3', ].join('&'); export const filteredTokens = [ @@ -33,6 +35,8 @@ export const filteredTokens = [ { type: 'confidential', value: { data: 'no', operator: OPERATOR_IS } }, { type: 'iteration', value: { data: 'season: #4', operator: OPERATOR_IS } }, { type: 'iteration', value: { data: 'season: #20', operator: OPERATOR_IS_NOT } }, + { type: 'weight', value: { data: '1', operator: OPERATOR_IS } }, + { type: 'weight', value: { data: '3', operator: OPERATOR_IS_NOT } }, { type: 'filtered-search-term', value: { data: 'find' } }, { type: 'filtered-search-term', value: { data: 'issues' } }, ]; @@ -50,6 +54,8 @@ export const apiParams = { confidential: 'no', iteration_title: 'season: #4', 'not[iteration_title]': 'season: #20', + weight: '1', + 'not[weight]': '3', }; export const urlParams = { @@ -65,4 +71,6 @@ export const urlParams = { confidential: ['no'], iteration_title: ['season: #4'], 'not[iteration_title]': ['season: #20'], + weight: ['1'], + 'not[weight]': ['3'], }; diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js index e5943dc20b31d0..c49a1ab68b1e0f 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/mock_data.js @@ -8,6 +8,7 @@ import EpicToken from '~/vue_shared/components/filtered_search_bar/tokens/epic_t import IterationToken from '~/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue'; import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue'; import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'; +import WeightToken from '~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue'; export const mockAuthor1 = { id: 1, @@ -165,6 +166,14 @@ export const mockMembershipToken = { ], }; +export const mockWeightToken = { + type: 'weight', + icon: 'weight', + title: 'Weight', + unique: true, + token: WeightToken, +}; + export const mockMembershipTokenOptionsWithoutTitles = { ...mockMembershipToken, options: [{ value: 'exclude' }, { value: 'only' }], diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/iteration_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/iteration_token_spec.js index 88e5e7fa983dca..ca5dc984ae0ad2 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/iteration_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/iteration_token_spec.js @@ -32,7 +32,7 @@ describe('IterationToken', () => { await wrapper.vm.$nextTick(); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); expect(tokenSegments).toHaveLength(3); // `Iteration` `=` `gitlab-org: #1` expect(tokenSegments.at(2).text()).toBe(title); diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/weight_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/weight_token_spec.js new file mode 100644 index 00000000000000..9a72be636cd47a --- /dev/null +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/weight_token_spec.js @@ -0,0 +1,37 @@ +import { GlFilteredSearchTokenSegment } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import WeightToken from '~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue'; +import { mockWeightToken } from '../mock_data'; + +jest.mock('~/flash'); + +describe('WeightToken', () => { + const weight = '3'; + let wrapper; + + const createComponent = ({ config = mockWeightToken, value = { data: '' } } = {}) => + mount(WeightToken, { + propsData: { + config, + value, + }, + provide: { + portalName: 'fake target', + alignSuggestions: function fakeAlignSuggestions() {}, + suggestionsListClass: 'custom-class', + }, + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders weight value', () => { + wrapper = createComponent({ value: { data: weight } }); + + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); + + expect(tokenSegments).toHaveLength(3); // `Weight` `=` `3` + expect(tokenSegments.at(2).text()).toBe(weight); + }); +}); -- GitLab From b0264975dd2951ec5d1bb6cc5c2aff08de5e3bbd Mon Sep 17 00:00:00 2001 From: Coung Ngo Date: Mon, 26 Apr 2021 11:24:52 +0100 Subject: [PATCH 3/5] Add feature disabled test --- ee/spec/helpers/ee/issues_helper_spec.rb | 64 +++++++++++++++++------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/ee/spec/helpers/ee/issues_helper_spec.rb b/ee/spec/helpers/ee/issues_helper_spec.rb index e624b37c8c9967..56af5037769bdf 100644 --- a/ee/spec/helpers/ee/issues_helper_spec.rb +++ b/ee/spec/helpers/ee/issues_helper_spec.rb @@ -124,24 +124,52 @@ end describe '#issues_list_data' do - it 'returns expected result' do - current_user = double.as_null_object - finder = double.as_null_object - allow(helper).to receive(:current_user).and_return(current_user) - allow(helper).to receive(:finder).and_return(finder) - allow(helper).to receive(:can?).and_return(true) - allow(helper).to receive(:url_for).and_return('#') - allow(helper).to receive(:import_csv_namespace_project_issues_path).and_return('#') - allow(project).to receive(:feature_available?).and_return(true) - - expected = { - has_blocked_issues_feature: 'true', - has_issuable_health_status_feature: 'true', - has_issue_weights_feature: 'true', - project_iterations_path: api_v4_projects_iterations_path(id: project.id) - } - - expect(helper.issues_list_data(project, current_user, finder)).to include(expected) + context 'when features are enabled' do + it 'returns expected result' do + current_user = double.as_null_object + finder = double.as_null_object + allow(helper).to receive(:current_user).and_return(current_user) + allow(helper).to receive(:finder).and_return(finder) + allow(helper).to receive(:can?).and_return(true) + allow(helper).to receive(:url_for).and_return('#') + allow(helper).to receive(:import_csv_namespace_project_issues_path).and_return('#') + allow(project).to receive(:feature_available?).and_return(true) + + expected = { + has_blocked_issues_feature: 'true', + has_issuable_health_status_feature: 'true', + has_issue_weights_feature: 'true', + project_iterations_path: api_v4_projects_iterations_path(id: project.id) + } + + expect(helper.issues_list_data(project, current_user, finder)).to include(expected) + end + end + + context 'when features are disabled' do + it 'returns expected result' do + current_user = double.as_null_object + finder = double.as_null_object + allow(helper).to receive(:current_user).and_return(current_user) + allow(helper).to receive(:finder).and_return(finder) + allow(helper).to receive(:can?).and_return(true) + allow(helper).to receive(:url_for).and_return('#') + allow(helper).to receive(:import_csv_namespace_project_issues_path).and_return('#') + allow(project).to receive(:feature_available?).and_return(false) + + expected = { + has_blocked_issues_feature: 'false', + has_issuable_health_status_feature: 'false', + has_issue_weights_feature: 'false' + } + + not_expected = { + project_iterations_path: api_v4_projects_iterations_path(id: project.id) + } + + expect(helper.issues_list_data(project, current_user, finder)).to include(expected) + expect(helper.issues_list_data(project, current_user, finder)).not_to include(not_expected) + end end end end -- GitLab From 90974ffe2315abff789cf6d1e96d4c344ef9558c Mon Sep 17 00:00:00 2001 From: Coung Ngo Date: Tue, 27 Apr 2021 12:55:30 +0100 Subject: [PATCH 4/5] Update issues_helper_spec test --- ee/spec/helpers/ee/issues_helper_spec.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ee/spec/helpers/ee/issues_helper_spec.rb b/ee/spec/helpers/ee/issues_helper_spec.rb index 56af5037769bdf..4df7bc1602e96a 100644 --- a/ee/spec/helpers/ee/issues_helper_spec.rb +++ b/ee/spec/helpers/ee/issues_helper_spec.rb @@ -163,12 +163,9 @@ has_issue_weights_feature: 'false' } - not_expected = { - project_iterations_path: api_v4_projects_iterations_path(id: project.id) - } - - expect(helper.issues_list_data(project, current_user, finder)).to include(expected) - expect(helper.issues_list_data(project, current_user, finder)).not_to include(not_expected) + result = helper.issues_list_data(project, current_user, finder) + expect(result).to include(expected) + expect(result).not_to include(:project_iterations_path) end end end -- GitLab From 639f54e6a42e3dedeb3a1600f52d76128ba210d6 Mon Sep 17 00:00:00 2001 From: Coung Ngo Date: Thu, 29 Apr 2021 10:07:33 +0100 Subject: [PATCH 5/5] Improve rspec test --- ee/spec/helpers/ee/issues_helper_spec.rb | 36 ++++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/ee/spec/helpers/ee/issues_helper_spec.rb b/ee/spec/helpers/ee/issues_helper_spec.rb index 4df7bc1602e96a..544c1c79c7094c 100644 --- a/ee/spec/helpers/ee/issues_helper_spec.rb +++ b/ee/spec/helpers/ee/issues_helper_spec.rb @@ -124,17 +124,22 @@ end describe '#issues_list_data' do + let(:current_user) { double.as_null_object } + let(:finder) { double.as_null_object } + + before do + allow(helper).to receive(:current_user).and_return(current_user) + allow(helper).to receive(:can?).and_return(true) + allow(helper).to receive(:url_for).and_return('#') + allow(helper).to receive(:import_csv_namespace_project_issues_path).and_return('#') + end + context 'when features are enabled' do - it 'returns expected result' do - current_user = double.as_null_object - finder = double.as_null_object - allow(helper).to receive(:current_user).and_return(current_user) - allow(helper).to receive(:finder).and_return(finder) - allow(helper).to receive(:can?).and_return(true) - allow(helper).to receive(:url_for).and_return('#') - allow(helper).to receive(:import_csv_namespace_project_issues_path).and_return('#') - allow(project).to receive(:feature_available?).and_return(true) + before do + stub_licensed_features(iterations: true, issue_weights: true, issuable_health_status: true, blocked_issues: true) + end + it 'returns data with licensed features enabled' do expected = { has_blocked_issues_feature: 'true', has_issuable_health_status_feature: 'true', @@ -147,16 +152,11 @@ end context 'when features are disabled' do - it 'returns expected result' do - current_user = double.as_null_object - finder = double.as_null_object - allow(helper).to receive(:current_user).and_return(current_user) - allow(helper).to receive(:finder).and_return(finder) - allow(helper).to receive(:can?).and_return(true) - allow(helper).to receive(:url_for).and_return('#') - allow(helper).to receive(:import_csv_namespace_project_issues_path).and_return('#') - allow(project).to receive(:feature_available?).and_return(false) + before do + stub_licensed_features(iterations: false, issue_weights: false, issuable_health_status: false, blocked_issues: false) + end + it 'returns data with licensed features disabled' do expected = { has_blocked_issues_feature: 'false', has_issuable_health_status_feature: 'false', -- GitLab