diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-11-30 15:23:27 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-11-30 15:23:27 +0300 |
commit | 3bba41a8c5dfcca0d086eaef10ef36a705dd4f7a (patch) | |
tree | 81954681947aaa85592fa7f3c9beed23a7b6bb01 /spec | |
parent | 1aa447601c6be1e964acbb674887649dab23b804 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
43 files changed, 425 insertions, 1178 deletions
diff --git a/spec/components/projects/ml/show_ml_model_component_spec.rb b/spec/components/projects/ml/show_ml_model_component_spec.rb index ec125851d3d..02fad55e0be 100644 --- a/spec/components/projects/ml/show_ml_model_component_spec.rb +++ b/spec/components/projects/ml/show_ml_model_component_spec.rb @@ -25,7 +25,10 @@ RSpec.describe Projects::Ml::ShowMlModelComponent, type: :component, feature_cat 'path' => "/#{project.full_path}/-/ml/models/#{model1.id}", 'description' => 'This is a placeholder for the short description', 'latestVersion' => { - 'version' => model1.latest_version.version + 'version' => model1.latest_version.version, + 'description' => model1.latest_version.description, + 'projectPath' => "/#{project.full_path}", + 'packageId' => model1.latest_version.package_id }, 'versionCount' => 1 } diff --git a/spec/components/projects/ml/show_ml_model_version_component_spec.rb b/spec/components/projects/ml/show_ml_model_version_component_spec.rb index 973d8123c45..a7dad5e4b2b 100644 --- a/spec/components/projects/ml/show_ml_model_version_component_spec.rb +++ b/spec/components/projects/ml/show_ml_model_version_component_spec.rb @@ -5,7 +5,7 @@ require "spec_helper" RSpec.describe Projects::Ml::ShowMlModelVersionComponent, type: :component, feature_category: :mlops do let_it_be(:project) { build_stubbed(:project) } let_it_be(:model) { build_stubbed(:ml_models, project: project) } - let_it_be(:version) { build_stubbed(:ml_model_versions, model: model) } + let_it_be(:version) { build_stubbed(:ml_model_versions, :with_package, model: model, description: 'abc') } subject(:component) do described_class.new(model_version: version) @@ -23,7 +23,10 @@ RSpec.describe Projects::Ml::ShowMlModelVersionComponent, type: :component, feat 'modelVersion' => { 'id' => version.id, 'version' => version.version, + 'description' => 'abc', + 'projectPath' => "/#{project.full_path}", 'path' => "/#{project.full_path}/-/ml/models/#{model.id}/versions/#{version.id}", + 'packageId' => version.package_id, 'model' => { 'name' => model.name, 'path' => "/#{project.full_path}/-/ml/models/#{model.id}" diff --git a/spec/factories/ml/model_versions.rb b/spec/factories/ml/model_versions.rb index 456d1b1e913..a097640b134 100644 --- a/spec/factories/ml/model_versions.rb +++ b/spec/factories/ml/model_versions.rb @@ -6,6 +6,7 @@ FactoryBot.define do model { association :ml_models } project { model.project } + description { 'Some description' } trait :with_package do package do diff --git a/spec/features/frequently_visited_projects_and_groups_spec.rb b/spec/features/frequently_visited_projects_and_groups_spec.rb index 514b642a2d4..764e88882a8 100644 --- a/spec/features/frequently_visited_projects_and_groups_spec.rb +++ b/spec/features/frequently_visited_projects_and_groups_spec.rb @@ -3,8 +3,6 @@ require 'spec_helper' RSpec.describe 'Frequently visited items', :js, feature_category: :shared do - include Features::TopNavSpecHelpers - let_it_be(:user) { create(:user) } before do diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index a3cbb86da2c..1c25fce5270 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -3,8 +3,6 @@ require 'spec_helper' RSpec.describe 'New project', :js, feature_category: :groups_and_projects do - include Features::TopNavSpecHelpers - before do stub_application_setting(import_sources: Gitlab::ImportSources.values) end diff --git a/spec/features/projects/show/user_sees_git_instructions_spec.rb b/spec/features/projects/show/user_sees_git_instructions_spec.rb index 5e6857843a6..4933b3f239c 100644 --- a/spec/features/projects/show/user_sees_git_instructions_spec.rb +++ b/spec/features/projects/show/user_sees_git_instructions_spec.rb @@ -10,6 +10,7 @@ RSpec.describe 'Projects > Show > User sees Git instructions', feature_category: # validation failure on NotificationSetting. # See https://gitlab.com/gitlab-org/gitlab/-/issues/299822#note_492817174 user.notification_settings.reset + stub_feature_flags(project_overview_reorg: false) end shared_examples_for 'redirects to the sign in page' do diff --git a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb index 41eab966895..674c7db83f1 100644 --- a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb +++ b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb @@ -17,8 +17,8 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons', feature_cat describe 'as a normal user' do before do + stub_feature_flags(project_overview_reorg: false) sign_in(user) - visit project_path(project) end @@ -40,6 +40,7 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons', feature_cat describe 'as a maintainer' do before do + stub_feature_flags(project_overview_reorg: false) project.add_maintainer(user) sign_in(user) diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index c6966e47f0a..77ba5c53a35 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -12,6 +12,7 @@ RSpec.describe 'Project', feature_category: :groups_and_projects do before do sign_in user visit new_project_path + stub_feature_flags(project_overview_reorg: false) end shared_examples 'creates from template' do |template, sub_template_tab = nil| @@ -99,6 +100,7 @@ RSpec.describe 'Project', feature_category: :groups_and_projects do before do sign_in(project.first_owner) + stub_feature_flags(project_overview_reorg: false) end it 'parses Markdown' do @@ -164,6 +166,7 @@ RSpec.describe 'Project', feature_category: :groups_and_projects do before do sign_in(project.first_owner) visit path + stub_feature_flags(project_overview_reorg: false) end it 'shows project topics' do @@ -195,6 +198,7 @@ RSpec.describe 'Project', feature_category: :groups_and_projects do before do sign_in(project.first_owner) visit path + stub_feature_flags(project_overview_reorg: false) end context 'desktop component' do @@ -427,6 +431,10 @@ RSpec.describe 'Project', feature_category: :groups_and_projects do let(:project) { create(:project, :repository) } let(:user) { create(:user) } + before do + stub_feature_flags(project_overview_reorg: false) + end + it 'does not contain default branch information in its content', :js do default_branch = 'merge-commit-analyze-side-branch' diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb index 83eb7cb989e..5d121d9eeba 100644 --- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb @@ -18,8 +18,10 @@ RSpec.describe 'User uploads avatar to profile', feature_category: :user_profile wait_for_all_requests - data_uri = find('.avatar-image .gl-avatar')['src'] - within_testid('user-dropdown') { expect(find('.gl-avatar')['src']).to eq data_uri } + within_testid('user-dropdown') do + # We are setting a blob URL + expect(find('.gl-avatar')['src']).to start_with 'blob:' + end visit profile_path diff --git a/spec/frontend/environments/kubernetes_overview_spec.js b/spec/frontend/environments/kubernetes_overview_spec.js index 12689df586f..9f4a7518c47 100644 --- a/spec/frontend/environments/kubernetes_overview_spec.js +++ b/spec/frontend/environments/kubernetes_overview_spec.js @@ -149,14 +149,14 @@ describe('~/environments/components/kubernetes_overview.vue', () => { }); it('sets `clusterHealthStatus` as error when pods emitted a failure', async () => { - findKubernetesPods().vm.$emit('failed'); + findKubernetesPods().vm.$emit('update-failed-state', { pods: true }); await nextTick(); expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('error'); }); it('sets `clusterHealthStatus` as error when workload types emitted a failure', async () => { - findKubernetesTabs().vm.$emit('failed'); + findKubernetesTabs().vm.$emit('update-failed-state', { summary: true }); await nextTick(); expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('error'); @@ -165,6 +165,21 @@ describe('~/environments/components/kubernetes_overview.vue', () => { it('sets `clusterHealthStatus` as success when data is loaded and no failures where emitted', () => { expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('success'); }); + + it('sets `clusterHealthStatus` as success after state update if there are no failures', async () => { + findKubernetesTabs().vm.$emit('update-failed-state', { summary: true }); + findKubernetesTabs().vm.$emit('update-failed-state', { pods: true }); + await nextTick(); + expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('error'); + + findKubernetesTabs().vm.$emit('update-failed-state', { summary: false }); + await nextTick(); + expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('error'); + + findKubernetesTabs().vm.$emit('update-failed-state', { pods: false }); + await nextTick(); + expect(findKubernetesStatusBar().props('clusterHealthStatus')).toBe('success'); + }); }); describe('on cluster error', () => { diff --git a/spec/frontend/environments/kubernetes_pods_spec.js b/spec/frontend/environments/kubernetes_pods_spec.js index 2952e0c68c2..6c3e49e4d8a 100644 --- a/spec/frontend/environments/kubernetes_pods_spec.js +++ b/spec/frontend/environments/kubernetes_pods_spec.js @@ -90,11 +90,17 @@ describe('~/environments/components/kubernetes_pods.vue', () => { ]); }); - it('emits a failed event when there are failed pods', async () => { + it('emits a update-failed-state event for each pod', async () => { createWrapper(); await waitForPromises(); - expect(wrapper.emitted('failed')).toHaveLength(1); + expect(wrapper.emitted('update-failed-state')).toHaveLength(4); + expect(wrapper.emitted('update-failed-state')).toEqual([ + [{ pods: false }], + [{ pods: false }], + [{ pods: false }], + [{ pods: true }], + ]); }); }); diff --git a/spec/frontend/environments/kubernetes_summary_spec.js b/spec/frontend/environments/kubernetes_summary_spec.js index efabd766001..0d448d0b6af 100644 --- a/spec/frontend/environments/kubernetes_summary_spec.js +++ b/spec/frontend/environments/kubernetes_summary_spec.js @@ -107,8 +107,8 @@ describe('~/environments/components/kubernetes_summary.vue', () => { ); }); - it('emits a failed event when there are failed workload types', () => { - expect(wrapper.emitted('failed')).toHaveLength(1); + it('emits a update-failed-state event when there are failed workload types', () => { + expect(wrapper.emitted('update-failed-state')).toEqual([[{ summary: true }]]); }); it('emits an error message when gets an error from the cluster_client API', async () => { diff --git a/spec/frontend/environments/kubernetes_tabs_spec.js b/spec/frontend/environments/kubernetes_tabs_spec.js index fecd6d2a8ee..bf029ad6a81 100644 --- a/spec/frontend/environments/kubernetes_tabs_spec.js +++ b/spec/frontend/environments/kubernetes_tabs_spec.js @@ -179,9 +179,10 @@ describe('~/environments/components/kubernetes_tabs.vue', () => { expect(wrapper.emitted('loading')[1]).toEqual([false]); }); - it('emits a failed event when gets it from the component', () => { - findKubernetesSummary().vm.$emit('failed'); - expect(wrapper.emitted('failed')).toHaveLength(1); + it('emits a state update event when gets it from the component', () => { + const eventData = { summary: true }; + findKubernetesSummary().vm.$emit('update-failed-state', eventData); + expect(wrapper.emitted('update-failed-state')).toEqual([[eventData]]); }); }); }); diff --git a/spec/frontend/ml/model_registry/apps/show_ml_model_spec.js b/spec/frontend/ml/model_registry/apps/show_ml_model_spec.js index 3bdbb5e39ae..d58fc70af64 100644 --- a/spec/frontend/ml/model_registry/apps/show_ml_model_spec.js +++ b/spec/frontend/ml/model_registry/apps/show_ml_model_spec.js @@ -2,6 +2,7 @@ import { GlBadge, GlTab } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { ShowMlModel } from '~/ml/model_registry/apps'; import ModelVersionList from '~/ml/model_registry/components/model_version_list.vue'; +import ModelVersionDetail from '~/ml/model_registry/components/model_version_detail.vue'; import TitleArea from '~/vue_shared/components/registry/title_area.vue'; import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue'; import { NO_VERSIONS_LABEL } from '~/ml/model_registry/translations'; @@ -16,6 +17,7 @@ const findDetailTab = () => wrapper.findAllComponents(GlTab).at(0); const findVersionsTab = () => wrapper.findAllComponents(GlTab).at(1); const findVersionsCountBadge = () => findVersionsTab().findComponent(GlBadge); const findModelVersionList = () => findVersionsTab().findComponent(ModelVersionList); +const findModelVersionDetail = () => findDetailTab().findComponent(ModelVersionDetail); const findCandidateTab = () => wrapper.findAllComponents(GlTab).at(2); const findCandidatesCountBadge = () => findCandidateTab().findComponent(GlBadge); const findTitleArea = () => wrapper.findComponent(TitleArea); @@ -47,7 +49,11 @@ describe('ShowMlModel', () => { describe('when it has latest version', () => { it('displays the version', () => { - expect(findDetailTab().text()).toContain(MODEL.latestVersion.version); + expect(findModelVersionDetail().props('modelVersion')).toBe(MODEL.latestVersion); + }); + + it('displays the title', () => { + expect(findDetailTab().text()).toContain('Latest version: 1.2.3'); }); }); @@ -59,6 +65,10 @@ describe('ShowMlModel', () => { it('shows no version message', () => { expect(findDetailTab().text()).toContain(NO_VERSIONS_LABEL); }); + + it('does not render model version detail', () => { + expect(findModelVersionDetail().exists()).toBe(false); + }); }); }); diff --git a/spec/frontend/ml/model_registry/apps/show_ml_model_version_spec.js b/spec/frontend/ml/model_registry/apps/show_ml_model_version_spec.js index 77fca53c00e..2605a75d961 100644 --- a/spec/frontend/ml/model_registry/apps/show_ml_model_version_spec.js +++ b/spec/frontend/ml/model_registry/apps/show_ml_model_version_spec.js @@ -1,5 +1,7 @@ import { shallowMount } from '@vue/test-utils'; import { ShowMlModelVersion } from '~/ml/model_registry/apps'; +import ModelVersionDetail from '~/ml/model_registry/components/model_version_detail.vue'; +import TitleArea from '~/vue_shared/components/registry/title_area.vue'; import { MODEL_VERSION } from '../mock_data'; let wrapper; @@ -7,9 +9,17 @@ const createWrapper = () => { wrapper = shallowMount(ShowMlModelVersion, { propsData: { modelVersion: MODEL_VERSION } }); }; -describe('ShowMlModelVersion', () => { +const findTitleArea = () => wrapper.findComponent(TitleArea); +const findModelVersionDetail = () => wrapper.findComponent(ModelVersionDetail); + +describe('ml/model_registry/apps/show_model_version.vue', () => { beforeEach(() => createWrapper()); - it('renders the app', () => { - expect(wrapper.text()).toContain(`${MODEL_VERSION.model.name} - ${MODEL_VERSION.version}`); + + it('renders the title', () => { + expect(findTitleArea().props('title')).toBe('blah / 1.2.3'); + }); + + it('renders the model version detail', () => { + expect(findModelVersionDetail().props('modelVersion')).toBe(MODEL_VERSION); }); }); diff --git a/spec/frontend/ml/model_registry/components/model_version_detail_spec.js b/spec/frontend/ml/model_registry/components/model_version_detail_spec.js new file mode 100644 index 00000000000..aeb9d13ad97 --- /dev/null +++ b/spec/frontend/ml/model_registry/components/model_version_detail_spec.js @@ -0,0 +1,50 @@ +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import ModelVersionDetail from '~/ml/model_registry/components/model_version_detail.vue'; +import PackageFiles from '~/packages_and_registries/package_registry/components/details/package_files.vue'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { makeModelVersion, MODEL_VERSION } from '../mock_data'; + +Vue.use(VueApollo); + +let wrapper; +const createWrapper = (modelVersion = MODEL_VERSION) => { + const apolloProvider = createMockApollo([]); + wrapper = shallowMount(ModelVersionDetail, { apolloProvider, propsData: { modelVersion } }); +}; + +const findPackageFiles = () => wrapper.findComponent(PackageFiles); + +describe('ml/model_registry/components/model_version_detail.vue', () => { + describe('description', () => { + beforeEach(() => createWrapper()); + + it('shows the description', () => { + expect(wrapper.text()).toContain(MODEL_VERSION.description); + }); + }); + + describe('package files', () => { + describe('if package exists', () => { + beforeEach(() => createWrapper()); + + it('renders files', () => { + expect(findPackageFiles().props()).toEqual({ + packageId: 'gid://gitlab/Packages::Package/12', + projectPath: MODEL_VERSION.projectPath, + packageType: 'ml_model', + canDelete: false, + }); + }); + }); + + describe('if package does not exist', () => { + beforeEach(() => createWrapper(makeModelVersion({ packageId: 0 }))); + + it('does not render files', () => { + expect(findPackageFiles().exists()).toBe(false); + }); + }); + }); +}); diff --git a/spec/frontend/ml/model_registry/mock_data.js b/spec/frontend/ml/model_registry/mock_data.js index cbf915cb5ed..7963e8aaa7b 100644 --- a/spec/frontend/ml/model_registry/mock_data.js +++ b/spec/frontend/ml/model_registry/mock_data.js @@ -14,7 +14,15 @@ export const makeModel = ({ latestVersion } = { latestVersion: LATEST_VERSION }) export const MODEL = makeModel(); -export const MODEL_VERSION = { version: '1.2.3', model: MODEL }; +export const makeModelVersion = ({ version = '1.2.3', model = MODEL, packageId = 12 } = {}) => ({ + version, + model, + packageId, + description: 'Model version description', + projectPath: 'path/to/project', +}); + +export const MODEL_VERSION = makeModelVersion(); export const mockModels = [ { diff --git a/spec/frontend/nav/components/responsive_app_spec.js b/spec/frontend/nav/components/responsive_app_spec.js deleted file mode 100644 index 9d3b43520ec..00000000000 --- a/spec/frontend/nav/components/responsive_app_spec.js +++ /dev/null @@ -1,122 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import ResponsiveApp from '~/nav/components/responsive_app.vue'; -import ResponsiveHeader from '~/nav/components/responsive_header.vue'; -import ResponsiveHome from '~/nav/components/responsive_home.vue'; -import TopNavContainerView from '~/nav/components/top_nav_container_view.vue'; -import { resetMenuItemsActive } from '~/nav/utils/reset_menu_items_active'; -import KeepAliveSlots from '~/vue_shared/components/keep_alive_slots.vue'; -import { TEST_NAV_DATA } from '../mock_data'; - -describe('~/nav/components/responsive_app.vue', () => { - let wrapper; - - const createComponent = () => { - wrapper = shallowMount(ResponsiveApp, { - propsData: { - navData: TEST_NAV_DATA, - }, - stubs: { - KeepAliveSlots, - }, - }); - }; - const findHome = () => wrapper.findComponent(ResponsiveHome); - const findMobileOverlay = () => wrapper.find('[data-testid="mobile-overlay"]'); - const findSubviewHeader = () => wrapper.findComponent(ResponsiveHeader); - const findSubviewContainer = () => wrapper.findComponent(TopNavContainerView); - const hasMobileOverlayVisible = () => findMobileOverlay().classes('mobile-nav-open'); - - beforeEach(() => { - document.body.innerHTML = ''; - // Add test class to reset state + assert that we're adding classes correctly - document.body.className = 'test-class'; - }); - - describe('default', () => { - beforeEach(() => { - createComponent(); - }); - - it('shows home by default', () => { - expect(findHome().isVisible()).toBe(true); - expect(findHome().props()).toEqual({ - navData: resetMenuItemsActive(TEST_NAV_DATA), - }); - }); - - it.each` - events | expectation - ${[]} | ${false} - ${['bv::dropdown::show']} | ${true} - ${['bv::dropdown::show', 'bv::dropdown::hide']} | ${false} - `( - 'with root events $events, movile overlay visible = $expectation', - async ({ events, expectation }) => { - // `await...reduce(async` is like doing an `forEach(async (...))` excpet it works - await events.reduce(async (acc, evt) => { - await acc; - - wrapper.vm.$root.$emit(evt); - - await nextTick(); - }, Promise.resolve()); - - expect(hasMobileOverlayVisible()).toBe(expectation); - }, - ); - }); - - const projectsContainerProps = { - containerClass: 'gl-px-3', - frequentItemsDropdownType: ResponsiveApp.FREQUENT_ITEMS_PROJECTS.namespace, - frequentItemsVuexModule: ResponsiveApp.FREQUENT_ITEMS_PROJECTS.vuexModule, - currentItem: {}, - linksPrimary: TEST_NAV_DATA.views.projects.linksPrimary, - linksSecondary: TEST_NAV_DATA.views.projects.linksSecondary, - }; - const groupsContainerProps = { - containerClass: 'gl-px-3', - frequentItemsDropdownType: ResponsiveApp.FREQUENT_ITEMS_GROUPS.namespace, - frequentItemsVuexModule: ResponsiveApp.FREQUENT_ITEMS_GROUPS.vuexModule, - currentItem: {}, - linksPrimary: TEST_NAV_DATA.views.groups.linksPrimary, - linksSecondary: TEST_NAV_DATA.views.groups.linksSecondary, - }; - - describe.each` - view | header | containerProps - ${'projects'} | ${'Projects'} | ${projectsContainerProps} - ${'groups'} | ${'Groups'} | ${groupsContainerProps} - `('when menu item with $view is clicked', ({ view, header, containerProps }) => { - beforeEach(async () => { - createComponent(); - - findHome().vm.$emit('menu-item-click', { view }); - - await nextTick(); - }); - - it('shows header', () => { - expect(findSubviewHeader().text()).toBe(header); - }); - - it('shows container subview', () => { - expect(findSubviewContainer().props()).toEqual(containerProps); - }); - - it('hides home', () => { - expect(findHome().isVisible()).toBe(false); - }); - - describe('when header back button is clicked', () => { - beforeEach(() => { - findSubviewHeader().vm.$emit('menu-item-click', { view: 'home' }); - }); - - it('shows home', () => { - expect(findHome().isVisible()).toBe(true); - }); - }); - }); -}); diff --git a/spec/frontend/nav/components/responsive_header_spec.js b/spec/frontend/nav/components/responsive_header_spec.js deleted file mode 100644 index 2514035270a..00000000000 --- a/spec/frontend/nav/components/responsive_header_spec.js +++ /dev/null @@ -1,63 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; -import ResponsiveHeader from '~/nav/components/responsive_header.vue'; -import TopNavMenuItem from '~/nav/components/top_nav_menu_item.vue'; - -const TEST_SLOT_CONTENT = 'Test slot content'; - -describe('~/nav/components/top_nav_menu_sections.vue', () => { - let wrapper; - - const createComponent = () => { - wrapper = shallowMount(ResponsiveHeader, { - slots: { - default: TEST_SLOT_CONTENT, - }, - directives: { - GlTooltip: createMockDirective('gl-tooltip'), - }, - }); - }; - - const findMenuItem = () => wrapper.findComponent(TopNavMenuItem); - - beforeEach(() => { - createComponent(); - }); - - it('renders slot', () => { - expect(wrapper.text()).toBe(TEST_SLOT_CONTENT); - }); - - it('renders back button', () => { - const button = findMenuItem(); - - const tooltip = getBinding(button.element, 'gl-tooltip').value.title; - - expect(tooltip).toBe('Go back'); - expect(button.props()).toEqual({ - menuItem: { - id: 'home', - view: 'home', - icon: 'chevron-lg-left', - }, - iconOnly: true, - }); - }); - - it('emits nothing', () => { - expect(wrapper.emitted()).toEqual({}); - }); - - describe('when back button is clicked', () => { - beforeEach(() => { - findMenuItem().vm.$emit('click'); - }); - - it('emits menu-item-click', () => { - expect(wrapper.emitted()).toEqual({ - 'menu-item-click': [[{ id: 'home', view: 'home', icon: 'chevron-lg-left' }]], - }); - }); - }); -}); diff --git a/spec/frontend/nav/components/responsive_home_spec.js b/spec/frontend/nav/components/responsive_home_spec.js deleted file mode 100644 index 5a5cfc93607..00000000000 --- a/spec/frontend/nav/components/responsive_home_spec.js +++ /dev/null @@ -1,133 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; -import ResponsiveHome from '~/nav/components/responsive_home.vue'; -import TopNavMenuItem from '~/nav/components/top_nav_menu_item.vue'; -import TopNavMenuSections from '~/nav/components/top_nav_menu_sections.vue'; -import TopNavNewDropdown from '~/nav/components/top_nav_new_dropdown.vue'; -import { TEST_NAV_DATA } from '../mock_data'; - -const TEST_SEARCH_MENU_ITEM = { - id: 'search', - title: 'search', - icon: 'search', - href: '/search', -}; - -const TEST_NEW_DROPDOWN_VIEW_MODEL = { - title: 'new', - menu_sections: [], -}; - -describe('~/nav/components/responsive_home.vue', () => { - let wrapper; - let menuItemClickListener; - - const createComponent = (props = {}) => { - wrapper = shallowMount(ResponsiveHome, { - propsData: { - navData: TEST_NAV_DATA, - ...props, - }, - directives: { - GlTooltip: createMockDirective('gl-tooltip'), - }, - listeners: { - 'menu-item-click': menuItemClickListener, - }, - }); - }; - - const findSearchMenuItem = () => wrapper.findComponent(TopNavMenuItem); - const findNewDropdown = () => wrapper.findComponent(TopNavNewDropdown); - const findMenuSections = () => wrapper.findComponent(TopNavMenuSections); - - beforeEach(() => { - menuItemClickListener = jest.fn(); - }); - - describe('default', () => { - beforeEach(() => { - createComponent(); - }); - - it.each` - desc | fn - ${'does not show search menu item'} | ${findSearchMenuItem} - ${'does not show new dropdown'} | ${findNewDropdown} - `('$desc', ({ fn }) => { - expect(fn().exists()).toBe(false); - }); - - it('shows menu sections', () => { - expect(findMenuSections().props('sections')).toEqual([ - { id: 'primary', menuItems: TEST_NAV_DATA.primary }, - { id: 'secondary', menuItems: TEST_NAV_DATA.secondary }, - ]); - }); - - it('emits when menu sections emits', () => { - expect(menuItemClickListener).not.toHaveBeenCalled(); - - findMenuSections().vm.$emit('menu-item-click', TEST_NAV_DATA.primary[0]); - - expect(menuItemClickListener).toHaveBeenCalledWith(TEST_NAV_DATA.primary[0]); - }); - }); - - describe('without secondary', () => { - beforeEach(() => { - createComponent({ navData: { ...TEST_NAV_DATA, secondary: null } }); - }); - - it('shows menu sections', () => { - expect(findMenuSections().props('sections')).toEqual([ - { id: 'primary', menuItems: TEST_NAV_DATA.primary }, - ]); - }); - }); - - describe('with search view', () => { - beforeEach(() => { - createComponent({ - navData: { - ...TEST_NAV_DATA, - views: { search: TEST_SEARCH_MENU_ITEM }, - }, - }); - }); - - it('shows search menu item', () => { - expect(findSearchMenuItem().props()).toEqual({ - menuItem: TEST_SEARCH_MENU_ITEM, - iconOnly: true, - }); - }); - - it('shows tooltip for search', () => { - const tooltip = getBinding(findSearchMenuItem().element, 'gl-tooltip'); - expect(tooltip.value).toEqual({ title: TEST_SEARCH_MENU_ITEM.title }); - }); - }); - - describe('with new view', () => { - beforeEach(() => { - createComponent({ - navData: { - ...TEST_NAV_DATA, - views: { new: TEST_NEW_DROPDOWN_VIEW_MODEL }, - }, - }); - }); - - it('shows new dropdown', () => { - expect(findNewDropdown().props()).toEqual({ - viewModel: TEST_NEW_DROPDOWN_VIEW_MODEL, - }); - }); - - it('shows tooltip for new dropdown', () => { - const tooltip = getBinding(findNewDropdown().element, 'gl-tooltip'); - expect(tooltip.value).toEqual({ title: TEST_NEW_DROPDOWN_VIEW_MODEL.title }); - }); - }); -}); diff --git a/spec/frontend/nav/components/top_nav_app_spec.js b/spec/frontend/nav/components/top_nav_app_spec.js deleted file mode 100644 index 7f39552eb42..00000000000 --- a/spec/frontend/nav/components/top_nav_app_spec.js +++ /dev/null @@ -1,68 +0,0 @@ -import { GlNavItemDropdown } from '@gitlab/ui'; -import { mount, shallowMount } from '@vue/test-utils'; -import { mockTracking } from 'helpers/tracking_helper'; -import TopNavApp from '~/nav/components/top_nav_app.vue'; -import TopNavDropdownMenu from '~/nav/components/top_nav_dropdown_menu.vue'; -import { TEST_NAV_DATA } from '../mock_data'; - -describe('~/nav/components/top_nav_app.vue', () => { - let wrapper; - - const createComponent = () => { - wrapper = mount(TopNavApp, { - propsData: { - navData: TEST_NAV_DATA, - }, - }); - }; - - const createComponentShallow = () => { - wrapper = shallowMount(TopNavApp, { - propsData: { - navData: TEST_NAV_DATA, - }, - }); - }; - - const findNavItemDropdown = () => wrapper.findComponent(GlNavItemDropdown); - const findNavItemDropdowToggle = () => findNavItemDropdown().find('.js-top-nav-dropdown-toggle'); - const findMenu = () => wrapper.findComponent(TopNavDropdownMenu); - - describe('default', () => { - beforeEach(() => { - createComponentShallow(); - }); - - it('renders nav item dropdown', () => { - expect(findNavItemDropdown().attributes('href')).toBeUndefined(); - expect(findNavItemDropdown().attributes()).toMatchObject({ - icon: '', - text: '', - 'no-flip': '', - 'no-caret': '', - }); - }); - - it('renders top nav dropdown menu', () => { - expect(findMenu().props()).toStrictEqual({ - primary: TEST_NAV_DATA.primary, - secondary: TEST_NAV_DATA.secondary, - views: TEST_NAV_DATA.views, - }); - }); - }); - - describe('tracking', () => { - it('emits a tracking event when the toggle is clicked', () => { - const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - createComponent(); - - findNavItemDropdowToggle().trigger('click'); - - expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_nav', { - label: 'hamburger_menu', - property: 'navigation_top', - }); - }); - }); -}); diff --git a/spec/frontend/nav/components/top_nav_container_view_spec.js b/spec/frontend/nav/components/top_nav_container_view_spec.js deleted file mode 100644 index 388ac243648..00000000000 --- a/spec/frontend/nav/components/top_nav_container_view_spec.js +++ /dev/null @@ -1,120 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { merge } from 'lodash'; -import { nextTick } from 'vue'; -import FrequentItemsApp from '~/frequent_items/components/app.vue'; -import { FREQUENT_ITEMS_PROJECTS } from '~/frequent_items/constants'; -import eventHub from '~/frequent_items/event_hub'; -import TopNavContainerView from '~/nav/components/top_nav_container_view.vue'; -import TopNavMenuSections from '~/nav/components/top_nav_menu_sections.vue'; -import VuexModuleProvider from '~/vue_shared/components/vuex_module_provider.vue'; -import { TEST_NAV_DATA } from '../mock_data'; - -const DEFAULT_PROPS = { - frequentItemsDropdownType: FREQUENT_ITEMS_PROJECTS.namespace, - frequentItemsVuexModule: FREQUENT_ITEMS_PROJECTS.vuexModule, - linksPrimary: TEST_NAV_DATA.primary, - linksSecondary: TEST_NAV_DATA.secondary, - containerClass: 'test-frequent-items-container-class', -}; -const TEST_OTHER_PROPS = { - namespace: 'projects', - currentUserName: 'test-user', - currentItem: { id: 'test' }, -}; - -describe('~/nav/components/top_nav_container_view.vue', () => { - let wrapper; - - const createComponent = (props = {}, options = {}) => { - wrapper = shallowMount(TopNavContainerView, { - propsData: { - ...DEFAULT_PROPS, - ...TEST_OTHER_PROPS, - ...props, - }, - ...options, - }); - }; - - const findMenuSections = () => wrapper.findComponent(TopNavMenuSections); - const findFrequentItemsApp = () => { - const parent = wrapper.findComponent(VuexModuleProvider); - - return { - vuexModule: parent.props('vuexModule'), - props: parent.findComponent(FrequentItemsApp).props(), - attributes: parent.findComponent(FrequentItemsApp).attributes(), - }; - }; - const findFrequentItemsContainer = () => wrapper.find('[data-testid="frequent-items-container"]'); - - it.each(['projects', 'groups'])( - 'emits frequent items event to event hub (%s)', - async (frequentItemsDropdownType) => { - const listener = jest.fn(); - eventHub.$on(`${frequentItemsDropdownType}-dropdownOpen`, listener); - createComponent({ frequentItemsDropdownType }); - - expect(listener).not.toHaveBeenCalled(); - - await nextTick(); - - expect(listener).toHaveBeenCalled(); - }, - ); - - describe('default', () => { - const EXTRA_ATTRS = { 'data-test-attribute': 'foo' }; - - beforeEach(() => { - createComponent({}, { attrs: EXTRA_ATTRS }); - }); - - it('does not inherit extra attrs', () => { - expect(wrapper.attributes()).toEqual({ - class: expect.any(String), - }); - }); - - it('renders frequent items app', () => { - expect(findFrequentItemsApp()).toEqual({ - vuexModule: DEFAULT_PROPS.frequentItemsVuexModule, - props: expect.objectContaining( - merge({ currentItem: { lastAccessedOn: Date.now() } }, TEST_OTHER_PROPS), - ), - attributes: expect.objectContaining(EXTRA_ATTRS), - }); - }); - - it('renders given container class', () => { - expect(findFrequentItemsContainer().classes(DEFAULT_PROPS.containerClass)).toBe(true); - }); - - it('renders menu sections', () => { - const sections = [ - { id: 'primary', menuItems: TEST_NAV_DATA.primary }, - { id: 'secondary', menuItems: TEST_NAV_DATA.secondary }, - ]; - - expect(findMenuSections().props()).toEqual({ - sections, - withTopBorder: true, - isPrimarySection: false, - }); - }); - }); - - describe('without secondary links', () => { - beforeEach(() => { - createComponent({ - linksSecondary: [], - }); - }); - - it('renders one menu item group', () => { - expect(findMenuSections().props('sections')).toEqual([ - { id: 'primary', menuItems: TEST_NAV_DATA.primary }, - ]); - }); - }); -}); diff --git a/spec/frontend/nav/components/top_nav_dropdown_menu_spec.js b/spec/frontend/nav/components/top_nav_dropdown_menu_spec.js deleted file mode 100644 index 1d516240306..00000000000 --- a/spec/frontend/nav/components/top_nav_dropdown_menu_spec.js +++ /dev/null @@ -1,146 +0,0 @@ -import { shallowMount, mount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import TopNavDropdownMenu from '~/nav/components/top_nav_dropdown_menu.vue'; -import TopNavMenuItem from '~/nav/components/top_nav_menu_item.vue'; -import TopNavMenuSections from '~/nav/components/top_nav_menu_sections.vue'; -import KeepAliveSlots from '~/vue_shared/components/keep_alive_slots.vue'; -import { TEST_NAV_DATA } from '../mock_data'; -import { stubComponent } from '../../__helpers__/stub_component'; - -describe('~/nav/components/top_nav_dropdown_menu.vue', () => { - let wrapper; - - const createComponent = (props = {}, mountFn = shallowMount) => { - wrapper = mountFn(TopNavDropdownMenu, { - propsData: { - primary: TEST_NAV_DATA.primary, - secondary: TEST_NAV_DATA.secondary, - views: TEST_NAV_DATA.views, - ...props, - }, - stubs: { - // Stub the keep-alive-slots so we don't render frequent items which uses a store - KeepAliveSlots: stubComponent(KeepAliveSlots), - }, - }); - }; - - const findMenuItems = () => wrapper.findAllComponents(TopNavMenuItem); - const findMenuSections = () => wrapper.findComponent(TopNavMenuSections); - const findMenuSidebar = () => wrapper.find('[data-testid="menu-sidebar"]'); - const findMenuSubview = () => wrapper.findComponent(KeepAliveSlots); - const hasFullWidthMenuSidebar = () => findMenuSidebar().classes('gl-w-full'); - - const withActiveIndex = (menuItems, activeIndex) => - menuItems.map((x, idx) => ({ - ...x, - active: idx === activeIndex, - })); - - beforeEach(() => { - jest.spyOn(console, 'error').mockImplementation(); - }); - - describe('default', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders menu sections', () => { - expect(findMenuSections().props()).toEqual({ - sections: [ - { id: 'primary', menuItems: TEST_NAV_DATA.primary }, - { id: 'secondary', menuItems: TEST_NAV_DATA.secondary }, - ], - withTopBorder: false, - isPrimarySection: true, - }); - }); - - it('has full width menu sidebar', () => { - expect(hasFullWidthMenuSidebar()).toBe(true); - }); - - it('renders hidden subview with no slot key', () => { - const subview = findMenuSubview(); - - expect(subview.isVisible()).toBe(false); - expect(subview.props()).toEqual({ slotKey: '' }); - }); - }); - - describe('with pre-initialized active view', () => { - beforeEach(() => { - // We opt for a small integration test, to make sure the event is handled correctly - // as it would in prod. - createComponent( - { - primary: withActiveIndex(TEST_NAV_DATA.primary, 1), - }, - mount, - ); - }); - - it('renders menu sections', () => { - expect(findMenuSections().props('sections')).toStrictEqual([ - { id: 'primary', menuItems: withActiveIndex(TEST_NAV_DATA.primary, 1) }, - { id: 'secondary', menuItems: TEST_NAV_DATA.secondary }, - ]); - }); - - it('does not have full width menu sidebar', () => { - expect(hasFullWidthMenuSidebar()).toBe(false); - }); - - it('renders visible subview with slot key', () => { - const subview = findMenuSubview(); - - expect(subview.isVisible()).toBe(true); - expect(subview.props('slotKey')).toBe(TEST_NAV_DATA.primary[1].view); - }); - - it('does not change view if non-view menu item is clicked', async () => { - const secondaryLink = findMenuItems().at(TEST_NAV_DATA.primary.length); - - // Ensure this doesn't have a view - expect(secondaryLink.props('menuItem').view).toBeUndefined(); - - secondaryLink.vm.$emit('click'); - - await nextTick(); - - expect(findMenuSubview().props('slotKey')).toBe(TEST_NAV_DATA.primary[1].view); - }); - - describe('when menu item is clicked', () => { - let primaryLink; - - beforeEach(async () => { - primaryLink = findMenuItems().at(0); - primaryLink.vm.$emit('click'); - await nextTick(); - }); - - it('clicked on link with view', () => { - expect(primaryLink.props('menuItem').view).toBe(TEST_NAV_DATA.views.projects.namespace); - }); - - it('changes active view', () => { - expect(findMenuSubview().props('slotKey')).toBe(TEST_NAV_DATA.primary[0].view); - }); - - it('changes active status on menu item', () => { - expect(findMenuSections().props('sections')).toStrictEqual([ - { - id: 'primary', - menuItems: withActiveIndex(TEST_NAV_DATA.primary, 0), - }, - { - id: 'secondary', - menuItems: withActiveIndex(TEST_NAV_DATA.secondary, -1), - }, - ]); - }); - }); - }); -}); diff --git a/spec/frontend/nav/components/top_nav_menu_item_spec.js b/spec/frontend/nav/components/top_nav_menu_item_spec.js deleted file mode 100644 index b9cf39b8c1d..00000000000 --- a/spec/frontend/nav/components/top_nav_menu_item_spec.js +++ /dev/null @@ -1,145 +0,0 @@ -import { GlButton, GlIcon } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import TopNavMenuItem from '~/nav/components/top_nav_menu_item.vue'; - -const TEST_MENU_ITEM = { - title: 'Cheeseburger', - icon: 'search', - href: '/pretty/good/burger', - view: 'burger-view', - data: { qa_selector: 'not-a-real-selector', method: 'post', testFoo: 'test' }, -}; - -describe('~/nav/components/top_nav_menu_item.vue', () => { - let listener; - let wrapper; - - const createComponent = (props = {}) => { - wrapper = shallowMount(TopNavMenuItem, { - propsData: { - menuItem: TEST_MENU_ITEM, - ...props, - }, - listeners: { - click: listener, - }, - }); - }; - - const findButton = () => wrapper.findComponent(GlButton); - const findButtonIcons = () => - findButton() - .findAllComponents(GlIcon) - .wrappers.map((x) => ({ - name: x.props('name'), - classes: x.classes(), - })); - - beforeEach(() => { - listener = jest.fn(); - }); - - describe('default', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders button href and text', () => { - const button = findButton(); - - expect(button.attributes('href')).toBe(TEST_MENU_ITEM.href); - expect(button.text()).toBe(TEST_MENU_ITEM.title); - }); - - it('renders button data attributes', () => { - const button = findButton(); - - expect(button.attributes()).toMatchObject({ - 'data-qa-selector': TEST_MENU_ITEM.data.qa_selector, - 'data-method': TEST_MENU_ITEM.data.method, - 'data-test-foo': TEST_MENU_ITEM.data.testFoo, - }); - }); - - it('passes listeners to button', () => { - expect(listener).not.toHaveBeenCalled(); - - findButton().vm.$emit('click', 'TEST'); - - expect(listener).toHaveBeenCalledWith('TEST'); - }); - - it('renders expected icons', () => { - expect(findButtonIcons()).toEqual([ - { - name: TEST_MENU_ITEM.icon, - classes: ['gl-mr-3!'], - }, - { - name: 'chevron-right', - classes: ['gl-ml-auto'], - }, - ]); - }); - }); - - describe('with icon-only', () => { - beforeEach(() => { - createComponent({ iconOnly: true }); - }); - - it('does not render title or view icon', () => { - expect(wrapper.text()).toBe(''); - }); - - it('only renders menuItem icon', () => { - expect(findButtonIcons()).toEqual([ - { - name: TEST_MENU_ITEM.icon, - classes: [], - }, - ]); - }); - }); - - describe.each` - desc | menuItem | expectedIcons - ${'with no icon'} | ${{ ...TEST_MENU_ITEM, icon: null }} | ${['chevron-right']} - ${'with no view'} | ${{ ...TEST_MENU_ITEM, view: null }} | ${[TEST_MENU_ITEM.icon]} - ${'with no icon or view'} | ${{ ...TEST_MENU_ITEM, view: null, icon: null }} | ${[]} - `('$desc', ({ menuItem, expectedIcons }) => { - beforeEach(() => { - createComponent({ menuItem }); - }); - - it(`renders expected icons ${JSON.stringify(expectedIcons)}`, () => { - expect(findButtonIcons().map((x) => x.name)).toEqual(expectedIcons); - }); - }); - - describe.each` - desc | active | cssClass | expectedClasses - ${'default'} | ${false} | ${''} | ${[]} - ${'with css class'} | ${false} | ${'test-css-class testing-123'} | ${['test-css-class', 'testing-123']} - ${'with css class & active'} | ${true} | ${'test-css-class'} | ${['test-css-class', ...TopNavMenuItem.ACTIVE_CLASS.split(' ')]} - `('$desc', ({ active, cssClass, expectedClasses }) => { - beforeEach(() => { - createComponent({ - menuItem: { - ...TEST_MENU_ITEM, - active, - css_class: cssClass, - }, - }); - }); - - it('renders expected classes', () => { - expect(wrapper.classes()).toStrictEqual([ - 'top-nav-menu-item', - 'gl-display-block', - 'gl-pr-3!', - ...expectedClasses, - ]); - }); - }); -}); diff --git a/spec/frontend/nav/components/top_nav_menu_sections_spec.js b/spec/frontend/nav/components/top_nav_menu_sections_spec.js deleted file mode 100644 index 7a3e58fd964..00000000000 --- a/spec/frontend/nav/components/top_nav_menu_sections_spec.js +++ /dev/null @@ -1,138 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import TopNavMenuSections from '~/nav/components/top_nav_menu_sections.vue'; - -const TEST_SECTIONS = [ - { - id: 'primary', - menuItems: [ - { type: 'header', title: 'Heading' }, - { type: 'item', id: 'test', href: '/test/href' }, - { type: 'header', title: 'Another Heading' }, - { type: 'item', id: 'foo' }, - { type: 'item', id: 'bar' }, - ], - }, - { - id: 'secondary', - menuItems: [ - { type: 'item', id: 'lorem' }, - { type: 'item', id: 'ipsum' }, - ], - }, -]; - -describe('~/nav/components/top_nav_menu_sections.vue', () => { - let wrapper; - - const createComponent = (props = {}) => { - wrapper = shallowMount(TopNavMenuSections, { - propsData: { - sections: TEST_SECTIONS, - ...props, - }, - }); - }; - - const findMenuItemModels = (parent) => - parent.findAll('[data-testid="menu-header"],[data-testid="menu-item"]').wrappers.map((x) => { - return { - menuItem: x.vm - ? { - type: 'item', - ...x.props('menuItem'), - } - : { - type: 'header', - title: x.text(), - }, - classes: x.classes(), - }; - }); - const findSectionModels = () => - wrapper.findAll('[data-testid="menu-section"]').wrappers.map((x) => ({ - classes: x.classes(), - menuItems: findMenuItemModels(x), - })); - - describe('default', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders sections with menu items', () => { - const headerClasses = ['gl-px-4', 'gl-py-2', 'gl-text-gray-900', 'gl-display-block']; - const itemClasses = ['gl-w-full']; - - expect(findSectionModels()).toEqual([ - { - classes: [], - menuItems: TEST_SECTIONS[0].menuItems.map((menuItem, index) => { - const classes = menuItem.type === 'header' ? [...headerClasses] : [...itemClasses]; - if (index > 0) classes.push(menuItem.type === 'header' ? 'gl-pt-3!' : 'gl-mt-1'); - return { - menuItem, - classes, - }; - }), - }, - { - classes: [ - ...TopNavMenuSections.BORDER_CLASSES.split(' '), - 'gl-border-gray-50', - 'gl-mt-3', - ], - menuItems: TEST_SECTIONS[1].menuItems.map((menuItem, index) => { - const classes = menuItem.type === 'header' ? [...headerClasses] : [...itemClasses]; - if (index > 0) classes.push(menuItem.type === 'header' ? 'gl-pt-3!' : 'gl-mt-1'); - return { - menuItem, - classes, - }; - }), - }, - ]); - }); - - it('when clicked menu item with href, does nothing', () => { - const menuItem = wrapper.findAll('[data-testid="menu-item"]').at(0); - - menuItem.vm.$emit('click'); - - expect(wrapper.emitted()).toEqual({}); - }); - - it('when clicked menu item without href, emits "menu-item-click"', () => { - const menuItem = wrapper.findAll('[data-testid="menu-item"]').at(1); - - menuItem.vm.$emit('click'); - - expect(wrapper.emitted('menu-item-click')).toEqual([[TEST_SECTIONS[0].menuItems[3]]]); - }); - }); - - describe('with withTopBorder=true', () => { - beforeEach(() => { - createComponent({ withTopBorder: true }); - }); - - it('renders border classes for top section', () => { - expect(findSectionModels().map((x) => x.classes)).toEqual([ - [...TopNavMenuSections.BORDER_CLASSES.split(' '), 'gl-border-gray-50'], - [...TopNavMenuSections.BORDER_CLASSES.split(' '), 'gl-border-gray-50', 'gl-mt-3'], - ]); - }); - }); - - describe('with isPrimarySection=true', () => { - beforeEach(() => { - createComponent({ isPrimarySection: true }); - }); - - it('renders border classes for top section', () => { - expect(findSectionModels().map((x) => x.classes)).toEqual([ - [], - [...TopNavMenuSections.BORDER_CLASSES.split(' '), 'gl-border-gray-100', 'gl-mt-3'], - ]); - }); - }); -}); diff --git a/spec/frontend/nav/components/top_nav_new_dropdown_spec.js b/spec/frontend/nav/components/top_nav_new_dropdown_spec.js deleted file mode 100644 index 432ee5e9ecd..00000000000 --- a/spec/frontend/nav/components/top_nav_new_dropdown_spec.js +++ /dev/null @@ -1,142 +0,0 @@ -import { GlDropdown } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import TopNavNewDropdown from '~/nav/components/top_nav_new_dropdown.vue'; -import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue'; -import { TOP_NAV_INVITE_MEMBERS_COMPONENT } from '~/invite_members/constants'; - -const TEST_VIEW_MODEL = { - title: 'Dropdown', - menu_sections: [ - { - title: 'Section 1', - menu_items: [ - { id: 'foo-1', title: 'Foo 1', href: '/foo/1' }, - { id: 'foo-2', title: 'Foo 2', href: '/foo/2' }, - { id: 'foo-3', title: 'Foo 3', href: '/foo/3' }, - ], - }, - { - title: 'Section 2', - menu_items: [ - { id: 'bar-1', title: 'Bar 1', href: '/bar/1' }, - { id: 'bar-2', title: 'Bar 2', href: '/bar/2' }, - { - id: 'invite', - title: '_invite members title_', - component: TOP_NAV_INVITE_MEMBERS_COMPONENT, - icon: '_icon_', - data: { - trigger_element: '_trigger_element_', - trigger_source: '_trigger_source_', - }, - }, - ], - }, - ], -}; - -describe('~/nav/components/top_nav_menu_sections.vue', () => { - let wrapper; - - const createComponent = (props = {}) => { - wrapper = shallowMount(TopNavNewDropdown, { - propsData: { - viewModel: TEST_VIEW_MODEL, - ...props, - }, - }); - }; - - const findDropdown = () => wrapper.findComponent(GlDropdown); - const findInviteMembersTrigger = () => wrapper.findComponent(InviteMembersTrigger); - const findDropdownContents = () => - findDropdown() - .findAll('[data-testid]') - .wrappers.map((child) => { - const type = child.attributes('data-testid'); - - if (type === 'divider') { - return { type }; - } - if (type === 'header') { - return { type, text: child.text() }; - } - - return { - type, - text: child.text(), - href: child.attributes('href'), - }; - }); - - describe('default', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders dropdown parent', () => { - expect(findDropdown().props()).toMatchObject({ - text: TEST_VIEW_MODEL.title, - textSrOnly: true, - icon: 'plus', - }); - }); - - it('renders dropdown content', () => { - const hrefItems = TEST_VIEW_MODEL.menu_sections[1].menu_items.filter((item) => - Boolean(item.href), - ); - - expect(findDropdownContents()).toEqual([ - { - type: 'header', - text: TEST_VIEW_MODEL.menu_sections[0].title, - }, - ...TEST_VIEW_MODEL.menu_sections[0].menu_items.map(({ title, href }) => ({ - type: 'item', - href, - text: title, - })), - { - type: 'divider', - }, - { - type: 'header', - text: TEST_VIEW_MODEL.menu_sections[1].title, - }, - ...hrefItems.map(({ title, href }) => ({ - type: 'item', - href, - text: title, - })), - ]); - expect(findInviteMembersTrigger().props()).toMatchObject({ - displayText: '_invite members title_', - icon: '_icon_', - triggerElement: 'dropdown-_trigger_element_', - triggerSource: '_trigger_source_', - }); - }); - }); - - describe('with only 1 section', () => { - beforeEach(() => { - createComponent({ - viewModel: { - ...TEST_VIEW_MODEL, - menu_sections: TEST_VIEW_MODEL.menu_sections.slice(0, 1), - }, - }); - }); - - it('renders dropdown content without headers and dividers', () => { - expect(findDropdownContents()).toEqual( - TEST_VIEW_MODEL.menu_sections[0].menu_items.map(({ title, href }) => ({ - type: 'item', - href, - text: title, - })), - ); - }); - }); -}); diff --git a/spec/frontend/nav/mock_data.js b/spec/frontend/nav/mock_data.js deleted file mode 100644 index 2052acfe001..00000000000 --- a/spec/frontend/nav/mock_data.js +++ /dev/null @@ -1,39 +0,0 @@ -import { range } from 'lodash'; - -export const TEST_NAV_DATA = { - menuTitle: 'Test Menu Title', - primary: [ - ...['projects', 'groups'].map((view) => ({ - id: view, - href: null, - title: view, - view, - })), - ...range(0, 2).map((idx) => ({ - id: `primary-link-${idx}`, - href: `/path/to/primary/${idx}`, - title: `Title ${idx}`, - })), - ], - secondary: range(0, 2).map((idx) => ({ - id: `secondary-link-${idx}`, - href: `/path/to/secondary/${idx}`, - title: `SecTitle ${idx}`, - })), - views: { - projects: { - namespace: 'projects', - currentUserName: '', - currentItem: {}, - linksPrimary: [{ id: 'project-link', href: '/path/to/projects', title: 'Project Link' }], - linksSecondary: [], - }, - groups: { - namespace: 'groups', - currentUserName: '', - currentItem: {}, - linksPrimary: [], - linksSecondary: [{ id: 'group-link', href: '/path/to/groups', title: 'Group Link' }], - }, - }, -}; diff --git a/spec/frontend/observability/client_spec.js b/spec/frontend/observability/client_spec.js index 57259fe61c2..9b8f100785b 100644 --- a/spec/frontend/observability/client_spec.js +++ b/spec/frontend/observability/client_spec.js @@ -439,10 +439,84 @@ describe('buildClient', () => { expect(axios.get).toHaveBeenCalledTimes(1); expect(axios.get).toHaveBeenCalledWith(metricsUrl, { withCredentials: true, + params: expect.any(URLSearchParams), }); expect(result).toEqual(mockResponse); }); + describe('query filter', () => { + beforeEach(() => { + axiosMock.onGet(metricsUrl).reply(200, { + metrics: [], + }); + }); + + it('does not set any query param without filters', async () => { + await client.fetchMetrics(); + + expect(getQueryParam()).toBe(''); + }); + + it('sets the start_with query param based on the search filter', async () => { + await client.fetchMetrics({ + filters: { search: [{ value: 'foo' }, { value: 'bar' }, { value: ' ' }] }, + }); + expect(getQueryParam()).toBe('starts_with=foo+bar'); + }); + + it('ignores empty search', async () => { + await client.fetchMetrics({ + filters: { + search: [{ value: ' ' }, { value: '' }, { value: null }, { value: undefined }], + }, + }); + expect(getQueryParam()).toBe(''); + }); + + it('ignores unsupported filters', async () => { + await client.fetchMetrics({ + filters: { + unsupportedFilter: [{ operator: '=', value: 'foo' }], + }, + }); + + expect(getQueryParam()).toBe(''); + }); + + it('ignores non-array search filters', async () => { + await client.fetchMetrics({ + filters: { + search: { value: 'foo' }, + }, + }); + + expect(getQueryParam()).toBe(''); + }); + + it('adds the search limit param if specified with the search filter', async () => { + await client.fetchMetrics({ + filters: { search: [{ value: 'foo' }] }, + limit: 50, + }); + expect(getQueryParam()).toBe('starts_with=foo&limit=50'); + }); + + it('does not add the search limit param if the search filter is missing', async () => { + await client.fetchMetrics({ + limit: 50, + }); + expect(getQueryParam()).toBe(''); + }); + + it('does not add the search limit param if the search filter is empty', async () => { + await client.fetchMetrics({ + limit: 50, + search: [{ value: ' ' }, { value: '' }, { value: null }, { value: undefined }], + }); + expect(getQueryParam()).toBe(''); + }); + }); + it('rejects if metrics are missing', async () => { axiosMock.onGet(metricsUrl).reply(200, {}); diff --git a/spec/frontend/profile/edit/components/profile_edit_app_spec.js b/spec/frontend/profile/edit/components/profile_edit_app_spec.js index 31a368aefa9..39bf597352b 100644 --- a/spec/frontend/profile/edit/components/profile_edit_app_spec.js +++ b/spec/frontend/profile/edit/components/profile_edit_app_spec.js @@ -3,7 +3,6 @@ import MockAdapter from 'axios-mock-adapter'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { readFileAsDataURL } from '~/lib/utils/file_utility'; import axios from '~/lib/utils/axios_utils'; import ProfileEditApp from '~/profile/edit/components/profile_edit_app.vue'; import UserAvatar from '~/profile/edit/components/user_avatar.vue'; @@ -103,6 +102,8 @@ describe('Profile Edit App', () => { }); it('syncs header avatars', async () => { + jest.spyOn(document, 'dispatchEvent'); + jest.spyOn(URL, 'createObjectURL'); mockAxios.onPut(stubbedProfilePath).reply(200, { message: successMessage, }); @@ -112,7 +113,8 @@ describe('Profile Edit App', () => { await waitForPromises(); - expect(readFileAsDataURL).toHaveBeenCalledWith(mockAvatarFile); + expect(URL.createObjectURL).toHaveBeenCalledWith(mockAvatarFile); + expect(document.dispatchEvent).toHaveBeenCalledWith(new CustomEvent('userAvatar:update')); }); it('contains changes from the status form', async () => { diff --git a/spec/frontend/read_more_spec.js b/spec/frontend/read_more_spec.js index 5f7bd32e231..9b25c56f193 100644 --- a/spec/frontend/read_more_spec.js +++ b/spec/frontend/read_more_spec.js @@ -1,4 +1,3 @@ -import htmlProjectsOverview from 'test_fixtures/projects/overview.html'; import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; import initReadMore from '~/read_more'; @@ -11,7 +10,12 @@ describe('Read more click-to-expand functionality', () => { describe('expands target element', () => { beforeEach(() => { - setHTMLFixture(htmlProjectsOverview); + setHTMLFixture(` + <p class="read-more-container">Target</p> + <button type="button" class="js-read-more-trigger"> + <span>Button text</span> + </button> + `); }); it('adds "is-expanded" class to target element', () => { diff --git a/spec/frontend/super_sidebar/components/user_menu_spec.js b/spec/frontend/super_sidebar/components/user_menu_spec.js index ba675c8b3f5..4af3247693b 100644 --- a/spec/frontend/super_sidebar/components/user_menu_spec.js +++ b/spec/frontend/super_sidebar/components/user_menu_spec.js @@ -78,6 +78,20 @@ describe('UserMenu component', () => { }); }); + it('updates avatar url on custom avatar update event', async () => { + const url = `${userMenuMockData.avatar_url}-new-avatar`; + + document.dispatchEvent(new CustomEvent('userAvatar:update', { detail: { url } })); + await nextTick(); + + const avatar = toggle.findComponent(GlAvatar); + expect(avatar.exists()).toBe(true); + expect(avatar.props()).toMatchObject({ + entityName: userMenuMockData.name, + src: url, + }); + }); + it('renders screen reader text', () => { expect(toggle.find('.gl-sr-only').text()).toBe(`${userMenuMockData.name} user’s menu`); }); diff --git a/spec/frontend/super_sidebar/utils_spec.js b/spec/frontend/super_sidebar/utils_spec.js index 690d34a966b..3e99099b663 100644 --- a/spec/frontend/super_sidebar/utils_spec.js +++ b/spec/frontend/super_sidebar/utils_spec.js @@ -1,18 +1,42 @@ import MockAdapter from 'axios-mock-adapter'; import * as Sentry from '~/sentry/sentry_browser_wrapper'; -import { trackContextAccess, ariaCurrent } from '~/super_sidebar/utils'; +import { getTopFrequentItems, trackContextAccess, ariaCurrent } from '~/super_sidebar/utils'; import axios from '~/lib/utils/axios_utils'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import AccessorUtilities from '~/lib/utils/accessor'; import { FREQUENT_ITEMS, FIFTEEN_MINUTES_IN_MS } from '~/frequent_items/constants'; import { HTTP_STATUS_OK, HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status'; import waitForPromises from 'helpers/wait_for_promises'; +import { unsortedFrequentItems, sortedFrequentItems } from '../frequent_items/mock_data'; jest.mock('~/sentry/sentry_browser_wrapper'); useLocalStorageSpy(); describe('Super sidebar utils spec', () => { + describe('getTopFrequentItems', () => { + const maxItems = 3; + + it.each([undefined, null, []])('returns empty array if `items` is %s', (items) => { + const result = getTopFrequentItems(items); + + expect(result.length).toBe(0); + }); + + it('returns the requested amount of items', () => { + const result = getTopFrequentItems(unsortedFrequentItems, maxItems); + + expect(result.length).toBe(maxItems); + }); + + it('sorts frequent items in order of frequency and lastAccessedOn', () => { + const result = getTopFrequentItems(unsortedFrequentItems, maxItems); + const expectedResult = sortedFrequentItems.slice(0, maxItems); + + expect(result).toEqual(expectedResult); + }); + }); + describe('trackContextAccess', () => { useLocalStorageSpy(); diff --git a/spec/frontend/vue_merge_request_widget/components/merge_checks_spec.js b/spec/frontend/vue_merge_request_widget/components/merge_checks_spec.js index d39098b27c2..b19095cc686 100644 --- a/spec/frontend/vue_merge_request_widget/components/merge_checks_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/merge_checks_spec.js @@ -138,7 +138,7 @@ describe('Merge request merge checks component', () => { it.each` identifier ${'conflict'} - ${'unresolved_discussions'} + ${'discussions_not_resolved'} ${'need_rebase'} ${'default'} `('renders $identifier merge check', async ({ identifier }) => { diff --git a/spec/helpers/stat_anchors_helper_spec.rb b/spec/helpers/stat_anchors_helper_spec.rb index f3830bf4172..41ac7509c39 100644 --- a/spec/helpers/stat_anchors_helper_spec.rb +++ b/spec/helpers/stat_anchors_helper_spec.rb @@ -8,6 +8,10 @@ RSpec.describe StatAnchorsHelper do describe '#stat_anchor_attrs' do subject { helper.stat_anchor_attrs(anchor) } + before do + stub_feature_flags(project_overview_reorg: false) + end + context 'when anchor is a link' do let(:anchor) { anchor_klass.new(true) } diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb index f70b38377d8..ffede2b6759 100644 --- a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb @@ -911,4 +911,18 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m expect(actual).to contain_exactly(migration) end end + + describe '#finalize_command' do + let_it_be(:migration) do + create( + :batched_background_migration, + gitlab_schema: :gitlab_main, + job_arguments: [['column_1'], ['column_1_convert_to_bigint']] + ) + end + + it 'generates the correct finalize command' do + expect(migration.finalize_command).to eq("sudo gitlab-rake gitlab:background_migrations:finalize[CopyColumnUsingBackgroundMigrationJob,events,id,'[[\"column_1\"]\\,[\"column_1_convert_to_bigint\"]]']") + end + end end diff --git a/spec/migrations/fix_broken_user_achievements_awarded_spec.rb b/spec/migrations/fix_broken_user_achievements_awarded_spec.rb new file mode 100644 index 00000000000..cb31ca44a9f --- /dev/null +++ b/spec/migrations/fix_broken_user_achievements_awarded_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe FixBrokenUserAchievementsAwarded, migration: :gitlab_main, feature_category: :user_profile do + let(:migration) { described_class.new } + + let(:users_table) { table(:users) } + let(:namespaces_table) { table(:namespaces) } + let(:achievements_table) { table(:achievements) } + let(:user_achievements_table) { table(:user_achievements) } + let(:namespace) { namespaces_table.create!(name: 'something', path: generate(:username)) } + let(:achievement) { achievements_table.create!(name: 'something', namespace_id: namespace.id) } + let(:user) { users_table.create!(username: generate(:username), projects_limit: 0) } + let(:awarding_user) do + users_table.create!(username: generate(:username), email: generate(:email), projects_limit: 0) + end + + let!(:user_achievement_invalid) do + user_achievements_table.create!(user_id: user.id, achievement_id: achievement.id, + awarded_by_user_id: awarding_user.id) + end + + let!(:user_achievement_valid) do + user_achievements_table.create!(user_id: user.id, achievement_id: achievement.id, + awarded_by_user_id: user.id) + end + + describe '#up' do + before do + awarding_user.delete + end + + it 'migrates the invalid user achievement' do + expect { migrate! } + .to change { user_achievement_invalid.reload.awarded_by_user_id } + .from(nil).to(Users::Internal.ghost.id) + end + + it 'does not migrate the valid user achievement' do + expect { migrate! } + .not_to change { user_achievement_valid.reload.awarded_by_user_id } + end + end +end diff --git a/spec/migrations/fix_broken_user_achievements_revoked_spec.rb b/spec/migrations/fix_broken_user_achievements_revoked_spec.rb new file mode 100644 index 00000000000..34517ae67b4 --- /dev/null +++ b/spec/migrations/fix_broken_user_achievements_revoked_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe FixBrokenUserAchievementsRevoked, migration: :gitlab_main, feature_category: :user_profile do + let(:migration) { described_class.new } + + let(:users_table) { table(:users) } + let(:namespaces_table) { table(:namespaces) } + let(:achievements_table) { table(:achievements) } + let(:user_achievements_table) { table(:user_achievements) } + let(:namespace) { namespaces_table.create!(name: 'something', path: generate(:username)) } + let(:achievement) { achievements_table.create!(name: 'something', namespace_id: namespace.id) } + let(:user) { users_table.create!(username: generate(:username), projects_limit: 0) } + let(:revoked_invalid) do + user_achievements_table.create!(user_id: user.id, achievement_id: achievement.id, revoked_at: Time.current) + end + + let(:revoked_valid) do + user_achievements_table.create!(user_id: user.id, achievement_id: achievement.id, revoked_at: Time.current, + revoked_by_user_id: user.id) + end + + let(:not_revoked) { user_achievements_table.create!(user_id: user.id, achievement_id: achievement.id) } + + describe '#up' do + it 'migrates the invalid user achievement' do + expect { migrate! } + .to change { revoked_invalid.reload.revoked_by_user_id } + .from(nil).to(Users::Internal.ghost.id) + end + + it 'does not migrate valid revoked user achievement' do + expect { migrate! } + .not_to change { revoked_valid.reload.revoked_by_user_id } + end + + it 'does not migrate the not revoked user achievement' do + expect { migrate! } + .not_to change { not_revoked.reload.revoked_by_user_id } + end + end +end diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index a9725a796bf..70f843be0e1 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -57,6 +57,7 @@ RSpec.describe ProjectMember, feature_category: :groups_and_projects do let_it_be(:developer) { create(:user) } let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } + let_it_be(:admin) { create(:admin) } before do project.add_owner(owner) @@ -74,6 +75,30 @@ RSpec.describe ProjectMember, feature_category: :groups_and_projects do end end + context 'when member can manage owners via admin' do + let(:user) { admin } + + context 'with admin mode', :enable_admin_mode do + it 'returns Gitlab::Access.options_with_owner' do + expect(access_levels).to eq(Gitlab::Access.options_with_owner) + end + end + + context 'without admin mode' do + it 'returns empty hash' do + expect(access_levels).to eq({}) + end + end + end + + context 'when user is not a project member' do + let(:user) { create(:user) } + + it 'return an empty hash' do + expect(access_levels).to eq({}) + end + end + context 'when member cannot manage owners' do let(:user) { maintainer } diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb index 48db41ea8e3..d7738023805 100644 --- a/spec/presenters/project_presenter_spec.rb +++ b/spec/presenters/project_presenter_spec.rb @@ -428,6 +428,10 @@ RSpec.describe ProjectPresenter do end describe '#new_file_anchor_data' do + before do + stub_feature_flags(project_overview_reorg: false) + end + it 'returns new file data if user can push' do project.add_developer(user) @@ -751,6 +755,7 @@ RSpec.describe ProjectPresenter do subject(:empty_repo_statistics_buttons) { presenter.empty_repo_statistics_buttons } before do + stub_feature_flags(project_overview_reorg: false) allow(project).to receive(:auto_devops_enabled?).and_return(false) end diff --git a/spec/services/users/migrate_records_to_ghost_user_service_spec.rb b/spec/services/users/migrate_records_to_ghost_user_service_spec.rb index d6fb7a2954d..57378c07dd7 100644 --- a/spec/services/users/migrate_records_to_ghost_user_service_spec.rb +++ b/spec/services/users/migrate_records_to_ghost_user_service_spec.rb @@ -143,6 +143,13 @@ RSpec.describe Users::MigrateRecordsToGhostUserService, feature_category: :user_ let(:created_record) { create(:release, author: user) } end end + + context 'for user achievements' do + include_examples 'migrating records to the ghost user', Achievements::UserAchievement, + [:awarded_by_user, :revoked_by_user] do + let(:created_record) { create(:user_achievement, awarded_by_user: user, revoked_by_user: user) } + end + end end context 'on post-migrate cleanups' do @@ -358,6 +365,16 @@ RSpec.describe Users::MigrateRecordsToGhostUserService, feature_category: :user_ expect(Issue).not_to exist(issue.id) end + + it 'migrates awarded and revoked fields of user achievements' do + user_achievement = create(:user_achievement, awarded_by_user: user, revoked_by_user: user) + + service.execute(hard_delete: true) + user_achievement.reload + + expect(user_achievement.revoked_by_user).to eq(Users::Internal.ghost) + expect(user_achievement.awarded_by_user).to eq(Users::Internal.ghost) + end end end end diff --git a/spec/support/helpers/features/top_nav_spec_helpers.rb b/spec/support/helpers/features/top_nav_spec_helpers.rb deleted file mode 100644 index ecc05189fb4..00000000000 --- a/spec/support/helpers/features/top_nav_spec_helpers.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -# These helpers help you interact within the Source Editor (single-file editor, snippets, etc.). -# -module Features - module TopNavSpecHelpers - def open_top_nav - find('.js-top-nav-dropdown-toggle').click - end - - def within_top_nav - within('.js-top-nav-dropdown-menu') do - yield - end - end - - def open_top_nav_projects - open_top_nav - - within_top_nav do - click_button('Projects') - end - end - - def open_top_nav_groups - open_top_nav - - within_top_nav do - click_button('Groups') - end - end - end -end diff --git a/spec/views/projects/_files.html.haml_spec.rb b/spec/views/projects/_files.html.haml_spec.rb index 96c6c2bdfab..870d436ca88 100644 --- a/spec/views/projects/_files.html.haml_spec.rb +++ b/spec/views/projects/_files.html.haml_spec.rb @@ -35,6 +35,8 @@ RSpec.describe 'projects/_files', feature_category: :groups_and_projects do before do allow(view).to receive(:current_user).and_return(user) allow(user).to receive(:project_shortcut_buttons).and_return(true) + + stub_feature_flags(project_overview_reorg: false) end it 'renders buttons' do @@ -45,6 +47,10 @@ RSpec.describe 'projects/_files', feature_category: :groups_and_projects do end context 'when rendered in the project overview page and there is no current user' do + before do + stub_feature_flags(project_overview_reorg: false) + end + it 'renders buttons' do render(template, is_project_overview: true) diff --git a/spec/views/projects/_home_panel.html.haml_spec.rb b/spec/views/projects/_home_panel.html.haml_spec.rb index e5081df4c22..a35c87cb4b3 100644 --- a/spec/views/projects/_home_panel.html.haml_spec.rb +++ b/spec/views/projects/_home_panel.html.haml_spec.rb @@ -100,6 +100,8 @@ RSpec.describe 'projects/_home_panel' do allow(view).to receive(:current_user).and_return(user) allow(view).to receive(:can?).with(user, :read_project, project).and_return(false) allow(project).to receive(:license_anchor_data).and_return(false) + + stub_feature_flags(project_overview_reorg: false) end context 'has no badges' do |