diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-11 21:11:57 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-11 21:11:57 +0300 |
commit | 603ee53dbdbd3adaced752e1a119eb40d64e9979 (patch) | |
tree | 7c3e912fe4c02ea464063a4a224b02bbcdeea580 /spec | |
parent | c79523e3952dad31a9ab8ccbb9e38466fec3aec3 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
23 files changed, 194 insertions, 55 deletions
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 34477a7bb68..388d7c2266b 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -878,30 +878,81 @@ RSpec.describe ProjectsController do end context 'with project feature attributes' do - using RSpec::Parameterized::TableSyntax + let(:initial_value) { ProjectFeature::PRIVATE } + let(:update_to) { ProjectFeature::ENABLED } - where(:feature, :initial_value, :update_to) do - :metrics_dashboard_access_level | ProjectFeature::PRIVATE | ProjectFeature::ENABLED - :container_registry_access_level | ProjectFeature::ENABLED | ProjectFeature::PRIVATE + before do + project.project_feature.update!(feature_access_level => initial_value) end - with_them do - it "updates the project_feature new" do - params = { - namespace_id: project.namespace, - id: project.path, - project: { - project_feature_attributes: { - "#{feature}": update_to - } + def update_project_feature + put :update, params: { + namespace_id: project.namespace, + id: project.path, + project: { + project_feature_attributes: { + feature_access_level.to_s => update_to } } + } + end - expect { put :update, params: params }.to change { - project.reload.project_feature.public_send(feature) + shared_examples 'feature update success' do + it 'updates access level successfully' do + expect { update_project_feature }.to change { + project.reload.project_feature.public_send(feature_access_level) }.from(initial_value).to(update_to) end end + + shared_examples 'feature update failure' do + it 'cannot update access level' do + expect { update_project_feature }.not_to change { + project.reload.project_feature.public_send(feature_access_level) + } + end + end + + where(:feature_access_level) do + %i[ + metrics_dashboard_access_level + container_registry_access_level + environments_access_level + feature_flags_access_level + ] + end + + with_them do + it_behaves_like 'feature update success' + end + + context 'for feature_access_level operations_access_level' do + let(:feature_access_level) { :operations_access_level } + + include_examples 'feature update failure' + end + + context 'with feature flag split_operations_visibility_permissions disabled' do + before do + stub_feature_flags(split_operations_visibility_permissions: false) + end + + context 'for feature_access_level operations_access_level' do + let(:feature_access_level) { :operations_access_level } + + include_examples 'feature update success' + end + + where(:feature_access_level) do + %i[ + environments_access_level feature_flags_access_level + ] + end + + with_them do + it_behaves_like 'feature update failure' + end + end end end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 107fa28a639..98dfecb2888 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -38,6 +38,7 @@ FactoryBot.define do container_registry_access_level { ProjectFeature::ENABLED } security_and_compliance_access_level { ProjectFeature::PRIVATE } environments_access_level { ProjectFeature::ENABLED } + feature_flags_access_level { ProjectFeature::ENABLED } # we can't assign the delegated `#ci_cd_settings` attributes directly, as the # `#ci_cd_settings` relation needs to be created first diff --git a/spec/frontend/boards/components/board_new_item_spec.js b/spec/frontend/boards/components/board_new_item_spec.js index 86cebc8a719..dc6581e9360 100644 --- a/spec/frontend/boards/components/board_new_item_spec.js +++ b/spec/frontend/boards/components/board_new_item_spec.js @@ -93,7 +93,7 @@ describe('BoardNewItem', () => { titleInput().setValue('Foo'); await glForm().trigger('submit'); - expect(wrapper.emitted('form-submit')).toBeTruthy(); + expect(wrapper.emitted('form-submit')).toHaveLength(1); expect(wrapper.emitted('form-submit')[0]).toEqual([ { title: 'Foo', @@ -131,7 +131,7 @@ describe('BoardNewItem', () => { await glForm().trigger('reset'); expect(titleInput().element.value).toBe(''); - expect(wrapper.emitted('form-cancel')).toBeTruthy(); + expect(wrapper.emitted('form-cancel')).toHaveLength(1); }); }); }); diff --git a/spec/frontend/clusters/agents/components/activity_history_item_spec.js b/spec/frontend/clusters/agents/components/activity_history_item_spec.js index 100a280d0cc..68f6f11aa8f 100644 --- a/spec/frontend/clusters/agents/components/activity_history_item_spec.js +++ b/spec/frontend/clusters/agents/components/activity_history_item_spec.js @@ -23,7 +23,7 @@ describe('ActivityHistoryItem', () => { }; const findHistoryItem = () => wrapper.findComponent(HistoryItem); - const findTimeAgo = () => wrapper.find(TimeAgoTooltip); + const findTimeAgo = () => wrapper.findComponent(TimeAgoTooltip); afterEach(() => { wrapper.destroy(); diff --git a/spec/frontend/clusters/agents/components/create_token_modal_spec.js b/spec/frontend/clusters/agents/components/create_token_modal_spec.js index ad48afe10b6..0d10801e80e 100644 --- a/spec/frontend/clusters/agents/components/create_token_modal_spec.js +++ b/spec/frontend/clusters/agents/components/create_token_modal_spec.js @@ -55,7 +55,7 @@ describe('CreateTokenModal', () => { const findAgentInstructions = () => findModal().findComponent(AgentToken); const findButtonByVariant = (variant) => findModal() - .findAll(GlButton) + .findAllComponents(GlButton) .wrappers.find((button) => button.props('variant') === variant); const findActionButton = () => findButtonByVariant('confirm'); const findCancelButton = () => wrapper.findByTestId('agent-token-close-button'); diff --git a/spec/frontend/clusters/agents/components/token_table_spec.js b/spec/frontend/clusters/agents/components/token_table_spec.js index 6caeaf5c192..334615f1818 100644 --- a/spec/frontend/clusters/agents/components/token_table_spec.js +++ b/spec/frontend/clusters/agents/components/token_table_spec.js @@ -136,8 +136,8 @@ describe('ClusterAgentTokenTable', () => { const token = tokens.at(lineNumber); expect(token.text()).toContain(description); - expect(token.find(GlTruncate).exists()).toBe(truncatesText); - expect(token.find(GlTooltip).exists()).toBe(hasTooltip); + expect(token.findComponent(GlTruncate).exists()).toBe(truncatesText); + expect(token.findComponent(GlTooltip).exists()).toBe(hasTooltip); }, ); diff --git a/spec/frontend/clusters/components/new_cluster_spec.js b/spec/frontend/clusters/components/new_cluster_spec.js index f9df70b9f87..ef39c90aaef 100644 --- a/spec/frontend/clusters/components/new_cluster_spec.js +++ b/spec/frontend/clusters/components/new_cluster_spec.js @@ -12,9 +12,9 @@ describe('NewCluster', () => { await nextTick(); }; - const findDescription = () => wrapper.find(GlSprintf); + const findDescription = () => wrapper.findComponent(GlSprintf); - const findLink = () => wrapper.find(GlLink); + const findLink = () => wrapper.findComponent(GlLink); beforeEach(() => { return createWrapper(); diff --git a/spec/frontend/clusters/forms/components/integration_form_spec.js b/spec/frontend/clusters/forms/components/integration_form_spec.js index 67d442bfdc5..b17886a5826 100644 --- a/spec/frontend/clusters/forms/components/integration_form_spec.js +++ b/spec/frontend/clusters/forms/components/integration_form_spec.js @@ -32,8 +32,8 @@ describe('ClusterIntegrationForm', () => { wrapper = null; }; - const findSubmitButton = () => wrapper.find(GlButton); - const findGlToggle = () => wrapper.find(GlToggle); + const findSubmitButton = () => wrapper.findComponent(GlButton); + const findGlToggle = () => wrapper.findComponent(GlToggle); afterEach(() => { destroyWrapper(); diff --git a/spec/frontend/diffs/store/utils_spec.js b/spec/frontend/diffs/store/utils_spec.js index 8852c6c62c5..3f870a98396 100644 --- a/spec/frontend/diffs/store/utils_spec.js +++ b/spec/frontend/diffs/store/utils_spec.js @@ -424,8 +424,8 @@ describe('DiffsStoreUtils', () => { expect(firstChar).not.toBe('+'); expect(firstChar).not.toBe('-'); - expect(preparedDiff.diff_files[0].renderIt).toBeTruthy(); - expect(preparedDiff.diff_files[0].collapsed).toBeFalsy(); + expect(preparedDiff.diff_files[0].renderIt).toBe(true); + expect(preparedDiff.diff_files[0].collapsed).toBe(false); }); it('guarantees an empty array for both diff styles', () => { @@ -506,8 +506,8 @@ describe('DiffsStoreUtils', () => { }); it('sets the renderIt and collapsed attribute on files', () => { - expect(preparedDiffFiles[0].renderIt).toBeTruthy(); - expect(preparedDiffFiles[0].collapsed).toBeFalsy(); + expect(preparedDiffFiles[0].renderIt).toBe(true); + expect(preparedDiffFiles[0].collapsed).toBeUndefined(); }); it('guarantees an empty array of lines for both diff styles', () => { diff --git a/spec/frontend/dropzone_input_spec.js b/spec/frontend/dropzone_input_spec.js index a633de9ef56..0fe70bac6b7 100644 --- a/spec/frontend/dropzone_input_spec.js +++ b/spec/frontend/dropzone_input_spec.js @@ -29,7 +29,9 @@ describe('dropzone_input', () => { it('returns valid dropzone when successfully initialize', () => { const dropzone = dropzoneInput($(TEMPLATE)); - expect(dropzone.version).toBeTruthy(); + expect(dropzone).toMatchObject({ + version: expect.any(String), + }); }); describe('handlePaste', () => { diff --git a/spec/frontend/error_tracking_settings/components/project_dropdown_spec.js b/spec/frontend/error_tracking_settings/components/project_dropdown_spec.js index 1ba5a505f57..b44af547658 100644 --- a/spec/frontend/error_tracking_settings/components/project_dropdown_spec.js +++ b/spec/frontend/error_tracking_settings/components/project_dropdown_spec.js @@ -41,23 +41,23 @@ describe('error tracking settings project dropdown', () => { describe('empty project list', () => { it('renders the dropdown', () => { - expect(wrapper.find('#project-dropdown').exists()).toBeTruthy(); - expect(wrapper.find(GlDropdown).exists()).toBeTruthy(); + expect(wrapper.find('#project-dropdown').exists()).toBe(true); + expect(wrapper.find(GlDropdown).exists()).toBe(true); }); it('shows helper text', () => { - expect(wrapper.find('.js-project-dropdown-label').exists()).toBeTruthy(); + expect(wrapper.find('.js-project-dropdown-label').exists()).toBe(true); expect(wrapper.find('.js-project-dropdown-label').text()).toContain( 'To enable project selection', ); }); it('does not show an error', () => { - expect(wrapper.find('.js-project-dropdown-error').exists()).toBeFalsy(); + expect(wrapper.find('.js-project-dropdown-error').exists()).toBe(false); }); it('does not contain any dropdown items', () => { - expect(wrapper.find(GlDropdownItem).exists()).toBeFalsy(); + expect(wrapper.find(GlDropdownItem).exists()).toBe(false); expect(wrapper.find(GlDropdown).props('text')).toBe('No projects available'); }); }); @@ -70,12 +70,12 @@ describe('error tracking settings project dropdown', () => { }); it('renders the dropdown', () => { - expect(wrapper.find('#project-dropdown').exists()).toBeTruthy(); - expect(wrapper.find(GlDropdown).exists()).toBeTruthy(); + expect(wrapper.find('#project-dropdown').exists()).toBe(true); + expect(wrapper.find(GlDropdown).exists()).toBe(true); }); it('contains a number of dropdown items', () => { - expect(wrapper.find(GlDropdownItem).exists()).toBeTruthy(); + expect(wrapper.find(GlDropdownItem).exists()).toBe(true); expect(wrapper.findAll(GlDropdownItem).length).toBe(2); }); }); @@ -89,8 +89,8 @@ describe('error tracking settings project dropdown', () => { }); it('does not show helper text', () => { - expect(wrapper.find('.js-project-dropdown-label').exists()).toBeFalsy(); - expect(wrapper.find('.js-project-dropdown-error').exists()).toBeFalsy(); + expect(wrapper.find('.js-project-dropdown-label').exists()).toBe(false); + expect(wrapper.find('.js-project-dropdown-error').exists()).toBe(false); }); }); @@ -105,8 +105,8 @@ describe('error tracking settings project dropdown', () => { }); it('displays a error', () => { - expect(wrapper.find('.js-project-dropdown-label').exists()).toBeFalsy(); - expect(wrapper.find('.js-project-dropdown-error').exists()).toBeTruthy(); + expect(wrapper.find('.js-project-dropdown-label').exists()).toBe(false); + expect(wrapper.find('.js-project-dropdown-error').exists()).toBe(true); }); }); }); diff --git a/spec/frontend/ide/stores/actions/tree_spec.js b/spec/frontend/ide/stores/actions/tree_spec.js index d43393875eb..6e8a03b47ad 100644 --- a/spec/frontend/ide/stores/actions/tree_spec.js +++ b/spec/frontend/ide/stores/actions/tree_spec.js @@ -134,7 +134,7 @@ describe('Multi-file store tree actions', () => { it('toggles the tree open', async () => { await store.dispatch('toggleTreeOpen', tree.path); - expect(tree.opened).toBeTruthy(); + expect(tree.opened).toBe(true); }); }); diff --git a/spec/frontend/ide/stores/mutations/merge_request_spec.js b/spec/frontend/ide/stores/mutations/merge_request_spec.js index afbe6770c0d..2af06835181 100644 --- a/spec/frontend/ide/stores/mutations/merge_request_spec.js +++ b/spec/frontend/ide/stores/mutations/merge_request_spec.js @@ -30,7 +30,7 @@ describe('IDE store merge request mutations', () => { const newMr = localState.projects.abcproject.mergeRequests[1]; expect(newMr.title).toBe('mr'); - expect(newMr.active).toBeTruthy(); + expect(newMr.active).toBe(true); }); it('keeps original data', () => { diff --git a/spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js b/spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js index ce98a16dbb7..16d4459f597 100644 --- a/spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js +++ b/spec/frontend/issuable/related_issues/components/add_issuable_form_spec.js @@ -157,8 +157,8 @@ describe('AddIssuableForm', () => { describe('categorized issuables', () => { it.each` issuableType | pathIdSeparator | contextHeader | contextFooter - ${issuableTypesMap.ISSUE} | ${PathIdSeparator.Issue} | ${'The current issue'} | ${'the following issue(s)'} - ${issuableTypesMap.EPIC} | ${PathIdSeparator.Epic} | ${'The current epic'} | ${'the following epic(s)'} + ${issuableTypesMap.ISSUE} | ${PathIdSeparator.Issue} | ${'The current issue'} | ${'the following issues'} + ${issuableTypesMap.EPIC} | ${PathIdSeparator.Epic} | ${'The current epic'} | ${'the following epics'} `( 'show header text as "$contextHeader" and footer text as "$contextFooter" issuableType is set to $issuableType', ({ issuableType, contextHeader, contextFooter }) => { diff --git a/spec/frontend/sidebar/assignees_spec.js b/spec/frontend/sidebar/assignees_spec.js index a4474ead956..c2aff456abb 100644 --- a/spec/frontend/sidebar/assignees_spec.js +++ b/spec/frontend/sidebar/assignees_spec.js @@ -70,7 +70,7 @@ describe('Assignee component', () => { wrapper.find('[data-testid="assign-yourself"]').trigger('click'); await nextTick(); - expect(wrapper.emitted('assign-self')).toBeTruthy(); + expect(wrapper.emitted('assign-self')).toHaveLength(1); }); }); diff --git a/spec/frontend/vue_shared/components/markdown/header_spec.js b/spec/frontend/vue_shared/components/markdown/header_spec.js index ffc0f873377..9831908f806 100644 --- a/spec/frontend/vue_shared/components/markdown/header_spec.js +++ b/spec/frontend/vue_shared/components/markdown/header_spec.js @@ -125,8 +125,8 @@ describe('Markdown field header component', () => { ), ]); - expect(wrapper.emitted('preview-markdown')).toBeFalsy(); - expect(wrapper.emitted('write-markdown')).toBeFalsy(); + expect(wrapper.emitted('preview-markdown')).toBeUndefined(); + expect(wrapper.emitted('write-markdown')).toBeUndefined(); }); it('blurs preview link after click', () => { diff --git a/spec/frontend/vue_shared/components/metric_images/metric_images_tab_spec.js b/spec/frontend/vue_shared/components/metric_images/metric_images_tab_spec.js index 2cefa77b72d..1789610dba9 100644 --- a/spec/frontend/vue_shared/components/metric_images/metric_images_tab_spec.js +++ b/spec/frontend/vue_shared/components/metric_images/metric_images_tab_spec.js @@ -114,7 +114,7 @@ describe('Metric images tab', () => { await waitForPromises(); - expect(findModal().attributes('visible')).toBeFalsy(); + expect(findModal().attributes('visible')).toBeUndefined(); }); it('should add files and url when selected', async () => { diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 200e93f8dff..214086a3823 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -967,7 +967,8 @@ RSpec.describe ProjectsHelper do showDefaultAwardEmojis: project.show_default_award_emojis?, securityAndComplianceAccessLevel: project.security_and_compliance_access_level, containerRegistryAccessLevel: project.project_feature.container_registry_access_level, - environmentsAccessLevel: project.project_feature.environments_access_level + environmentsAccessLevel: project.project_feature.environments_access_level, + featureFlagsAccessLevel: project.project_feature.feature_flags_access_level ) end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 1078d4587f7..72e13943bf5 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -584,6 +584,7 @@ ProjectFeature: - container_registry_access_level - package_registry_access_level - environments_access_level +- feature_flags_access_level - created_at - updated_at ProtectedBranch::MergeAccessLevel: diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb index 6730b7a02f3..48c391451ed 100644 --- a/spec/models/concerns/project_features_compatibility_spec.rb +++ b/spec/models/concerns/project_features_compatibility_spec.rb @@ -5,7 +5,9 @@ require 'spec_helper' RSpec.describe ProjectFeaturesCompatibility do let(:project) { create(:project) } let(:features_enabled) { %w(issues wiki builds merge_requests snippets security_and_compliance) } - let(:features) { features_enabled + %w(repository pages operations container_registry package_registry environments) } + let(:features) do + features_enabled + %w(repository pages operations container_registry package_registry environments feature_flags) + end # We had issues_enabled, snippets_enabled, builds_enabled, merge_requests_enabled and issues_enabled fields on projects table # All those fields got moved to a new table called project_feature and are now integers instead of booleans diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 2920278679f..05651f7d85a 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -833,6 +833,7 @@ RSpec.describe Project, factory_default: :keep do it { is_expected.to delegate_method(:container_registry_enabled?).to(:project_feature) } it { is_expected.to delegate_method(:container_registry_access_level).to(:project_feature) } it { is_expected.to delegate_method(:environments_access_level).to(:project_feature) } + it { is_expected.to delegate_method(:feature_flags_access_level).to(:project_feature) } describe 'read project settings' do %i( diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 01a0a96be30..c3817f67158 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -2100,6 +2100,77 @@ RSpec.describe ProjectPolicy do end end + describe 'feature flags feature' do + using RSpec::Parameterized::TableSyntax + + let(:guest_permissions) { [] } + + let(:developer_permissions) do + guest_permissions + [ + :read_feature_flag, :create_feature_flag, :update_feature_flag, :destroy_feature_flag, :admin_feature_flag, + :admin_feature_flags_user_lists + ] + end + + let(:maintainer_permissions) do + developer_permissions + [:admin_feature_flags_client] + end + + where(:project_visibility, :access_level, :role, :allowed) do + :public | ProjectFeature::ENABLED | :maintainer | true + :public | ProjectFeature::ENABLED | :developer | true + :public | ProjectFeature::ENABLED | :guest | true + :public | ProjectFeature::ENABLED | :anonymous | true + :public | ProjectFeature::PRIVATE | :maintainer | true + :public | ProjectFeature::PRIVATE | :developer | true + :public | ProjectFeature::PRIVATE | :guest | true + :public | ProjectFeature::PRIVATE | :anonymous | false + :public | ProjectFeature::DISABLED | :maintainer | false + :public | ProjectFeature::DISABLED | :developer | false + :public | ProjectFeature::DISABLED | :guest | false + :public | ProjectFeature::DISABLED | :anonymous | false + :internal | ProjectFeature::ENABLED | :maintainer | true + :internal | ProjectFeature::ENABLED | :developer | true + :internal | ProjectFeature::ENABLED | :guest | true + :internal | ProjectFeature::ENABLED | :anonymous | false + :internal | ProjectFeature::PRIVATE | :maintainer | true + :internal | ProjectFeature::PRIVATE | :developer | true + :internal | ProjectFeature::PRIVATE | :guest | true + :internal | ProjectFeature::PRIVATE | :anonymous | false + :internal | ProjectFeature::DISABLED | :maintainer | false + :internal | ProjectFeature::DISABLED | :developer | false + :internal | ProjectFeature::DISABLED | :guest | false + :internal | ProjectFeature::DISABLED | :anonymous | false + :private | ProjectFeature::ENABLED | :maintainer | true + :private | ProjectFeature::ENABLED | :developer | true + :private | ProjectFeature::ENABLED | :guest | false + :private | ProjectFeature::ENABLED | :anonymous | false + :private | ProjectFeature::PRIVATE | :maintainer | true + :private | ProjectFeature::PRIVATE | :developer | true + :private | ProjectFeature::PRIVATE | :guest | false + :private | ProjectFeature::PRIVATE | :anonymous | false + :private | ProjectFeature::DISABLED | :maintainer | false + :private | ProjectFeature::DISABLED | :developer | false + :private | ProjectFeature::DISABLED | :guest | false + :private | ProjectFeature::DISABLED | :anonymous | false + end + + with_them do + let(:current_user) { user_subject(role) } + let(:project) { project_subject(project_visibility) } + + it 'allows/disallows the abilities based on the feature flags access level' do + project.project_feature.update!(feature_flags_access_level: access_level) + + if allowed + expect_allowed(*permissions_abilities(role)) + else + expect_disallowed(*permissions_abilities(role)) + end + end + end + end + describe 'access_security_and_compliance' do context 'when the "Security & Compliance" is enabled' do before do @@ -2566,4 +2637,15 @@ RSpec.describe ProjectPolicy do anonymous end end + + def permissions_abilities(role) + case role + when :maintainer + maintainer_permissions + when :developer + developer_permissions + else + guest_permissions + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1b3ff4bf2b9..8acf3bcf9c0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -53,10 +53,8 @@ end require 'rainbow/ext/string' Rainbow.enabled = false -# Require JH first because we need override some EE methods with JH methods, -# if we load EE first, we can't find JH modules in prepend_mod method -require_relative('../jh/spec/spec_helper') if Gitlab.jh? require_relative('../ee/spec/spec_helper') if Gitlab.ee? +require_relative('../jh/spec/spec_helper') if Gitlab.jh? # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. |