diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-07-28 18:09:45 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-07-28 18:09:45 +0300 |
commit | d62fd6e04c272d48dccde4033529ca97c27502f6 (patch) | |
tree | e3bbea524f4bccb92048fd8a52a42b757618b57b /spec | |
parent | aaff41e10e8c03e545af9ba157e79f67686972a0 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
22 files changed, 597 insertions, 266 deletions
diff --git a/spec/frontend/search/sidebar/components/app_spec.js b/spec/frontend/search/sidebar/components/app_spec.js index ba492833ec4..aace9c8f83e 100644 --- a/spec/frontend/search/sidebar/components/app_spec.js +++ b/spec/frontend/search/sidebar/components/app_spec.js @@ -4,9 +4,10 @@ import Vuex from 'vuex'; import { MOCK_QUERY } from 'jest/search/mock_data'; import GlobalSearchSidebar from '~/search/sidebar/components/app.vue'; import IssuesFilters from '~/search/sidebar/components/issues_filters.vue'; +import MergeRequestsFilters from '~/search/sidebar/components/merge_requests_filters.vue'; +import BlobsFilters from '~/search/sidebar/components/blobs_filters.vue'; import ScopeLegacyNavigation from '~/search/sidebar/components/scope_legacy_navigation.vue'; import ScopeSidebarNavigation from '~/search/sidebar/components/scope_sidebar_navigation.vue'; -import LanguageFilter from '~/search/sidebar/components/language_filter/index.vue'; Vue.use(Vuex); @@ -17,7 +18,7 @@ describe('GlobalSearchSidebar', () => { currentScope: jest.fn(() => 'issues'), }; - const createComponent = (initialState = {}, featureFlags = {}) => { + const createComponent = (initialState = {}) => { const store = new Vuex.Store({ state: { urlQuery: MOCK_QUERY, @@ -28,19 +29,15 @@ describe('GlobalSearchSidebar', () => { wrapper = shallowMount(GlobalSearchSidebar, { store, - provide: { - glFeatures: { - ...featureFlags, - }, - }, }); }; const findSidebarSection = () => wrapper.find('section'); - const findFilters = () => wrapper.findComponent(IssuesFilters); + const findIssuesFilters = () => wrapper.findComponent(IssuesFilters); + const findMergeRequestsFilters = () => wrapper.findComponent(MergeRequestsFilters); + const findBlobsFilters = () => wrapper.findComponent(BlobsFilters); const findScopeLegacyNavigation = () => wrapper.findComponent(ScopeLegacyNavigation); const findScopeSidebarNavigation = () => wrapper.findComponent(ScopeSidebarNavigation); - const findLanguageAggregation = () => wrapper.findComponent(LanguageFilter); describe('renders properly', () => { describe('always', () => { @@ -53,23 +50,18 @@ describe('GlobalSearchSidebar', () => { }); describe.each` - scope | showFilters | showsLanguage - ${'issues'} | ${true} | ${false} - ${'merge_requests'} | ${true} | ${false} - ${'projects'} | ${false} | ${false} - ${'blobs'} | ${false} | ${true} - `('sidebar scope: $scope', ({ scope, showFilters, showsLanguage }) => { + scope | filter + ${'issues'} | ${findIssuesFilters} + ${'merge_requests'} | ${findMergeRequestsFilters} + ${'blobs'} | ${findBlobsFilters} + `('with sidebar $scope scope:', ({ scope, filter }) => { beforeEach(() => { getterSpies.currentScope = jest.fn(() => scope); createComponent({ urlQuery: { scope } }); }); - it(`${!showFilters ? "doesn't" : ''} shows filters`, () => { - expect(findFilters().exists()).toBe(showFilters); - }); - - it(`${!showsLanguage ? "doesn't" : ''} shows language filters`, () => { - expect(findLanguageAggregation().exists()).toBe(showsLanguage); + it(`shows filter ${filter.name.replace('find', '')}`, () => { + expect(filter().exists()).toBe(true); }); }); diff --git a/spec/frontend/search/sidebar/components/blobs_filters_spec.js b/spec/frontend/search/sidebar/components/blobs_filters_spec.js new file mode 100644 index 00000000000..ff93e6f32e4 --- /dev/null +++ b/spec/frontend/search/sidebar/components/blobs_filters_spec.js @@ -0,0 +1,28 @@ +import { shallowMount } from '@vue/test-utils'; +import BlobsFilters from '~/search/sidebar/components/blobs_filters.vue'; +import LanguageFilter from '~/search/sidebar/components/language_filter/index.vue'; +import FiltersTemplate from '~/search/sidebar/components/filters_template.vue'; + +describe('GlobalSearch BlobsFilters', () => { + let wrapper; + + const findLanguageFilter = () => wrapper.findComponent(LanguageFilter); + const findFiltersTemplate = () => wrapper.findComponent(FiltersTemplate); + + const createComponent = () => { + wrapper = shallowMount(BlobsFilters); + }; + + describe('Renders correctly', () => { + beforeEach(() => { + createComponent(); + }); + it('renders FiltersTemplate', () => { + expect(findLanguageFilter().exists()).toBe(true); + }); + + it('renders ConfidentialityFilter', () => { + expect(findFiltersTemplate().exists()).toBe(true); + }); + }); +}); diff --git a/spec/frontend/search/sidebar/components/filters_spec.js b/spec/frontend/search/sidebar/components/filters_spec.js index 546a84ff040..42508d7e216 100644 --- a/spec/frontend/search/sidebar/components/filters_spec.js +++ b/spec/frontend/search/sidebar/components/filters_spec.js @@ -1,4 +1,3 @@ -import { GlButton, GlLink } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import Vue from 'vue'; import Vuex from 'vuex'; @@ -36,11 +35,8 @@ describe('GlobalSearchSidebarFilters', () => { }); }; - const findSidebarForm = () => wrapper.find('form'); const findStatusFilter = () => wrapper.findComponent(StatusFilter); const findConfidentialityFilter = () => wrapper.findComponent(ConfidentialityFilter); - const findApplyButton = () => wrapper.findComponent(GlButton); - const findResetLinkButton = () => wrapper.findComponent(GlLink); describe('Renders correctly', () => { beforeEach(() => { @@ -53,82 +49,6 @@ describe('GlobalSearchSidebarFilters', () => { it('renders ConfidentialityFilter', () => { expect(findConfidentialityFilter().exists()).toBe(true); }); - - it('renders ApplyButton', () => { - expect(findApplyButton().exists()).toBe(true); - }); - }); - - describe('ApplyButton', () => { - describe('when sidebarDirty is false', () => { - beforeEach(() => { - createComponent({ sidebarDirty: false }); - }); - - it('disables the button', () => { - expect(findApplyButton().attributes('disabled')).toBeDefined(); - }); - }); - - describe('when sidebarDirty is true', () => { - beforeEach(() => { - createComponent({ sidebarDirty: true }); - }); - - it('enables the button', () => { - expect(findApplyButton().attributes('disabled')).toBe(undefined); - }); - }); - }); - - describe('ResetLinkButton', () => { - describe('with no filter selected', () => { - beforeEach(() => { - createComponent({ urlQuery: {} }); - }); - - it('does not render', () => { - expect(findResetLinkButton().exists()).toBe(false); - }); - }); - - describe('with filter selected', () => { - beforeEach(() => { - createComponent({ urlQuery: MOCK_QUERY }); - }); - - it('does render', () => { - expect(findResetLinkButton().exists()).toBe(true); - }); - }); - - describe('with filter selected and user updated query back to default', () => { - beforeEach(() => { - createComponent({ urlQuery: MOCK_QUERY, query: {} }); - }); - - it('does render', () => { - expect(findResetLinkButton().exists()).toBe(true); - }); - }); - }); - - describe('actions', () => { - beforeEach(() => { - createComponent({}); - }); - - it('clicking ApplyButton calls applyQuery', () => { - findSidebarForm().trigger('submit'); - - expect(actionSpies.applyQuery).toHaveBeenCalled(); - }); - - it('clicking ResetLinkButton calls resetQuery', () => { - findResetLinkButton().vm.$emit('click'); - - expect(actionSpies.resetQuery).toHaveBeenCalled(); - }); }); describe.each` diff --git a/spec/frontend/search/sidebar/components/filters_template_spec.js b/spec/frontend/search/sidebar/components/filters_template_spec.js new file mode 100644 index 00000000000..11c7c541b54 --- /dev/null +++ b/spec/frontend/search/sidebar/components/filters_template_spec.js @@ -0,0 +1,167 @@ +import { GlForm, GlButton, GlLink } from '@gitlab/ui'; +import Vue from 'vue'; +import Vuex from 'vuex'; + +import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { MOCK_QUERY, MOCK_AGGREGATIONS } from 'jest/search/mock_data'; + +import FiltersTemplate from '~/search/sidebar/components/filters_template.vue'; + +import { + TRACKING_ACTION_CLICK, + TRACKING_LABEL_APPLY, + TRACKING_LABEL_RESET, +} from '~/search/sidebar/constants/index'; + +Vue.use(Vuex); + +describe('GlobalSearchSidebarLanguageFilter', () => { + let wrapper; + let trackingSpy; + + const actionSpies = { + applyQuery: jest.fn(), + resetQuery: jest.fn(), + }; + + const getterSpies = { + currentScope: jest.fn(() => 'issues'), + }; + + const createComponent = (initialState) => { + const store = new Vuex.Store({ + state: { + query: MOCK_QUERY, + urlQuery: MOCK_QUERY, + aggregations: MOCK_AGGREGATIONS, + sidebarDirty: false, + ...initialState, + }, + actions: actionSpies, + getters: getterSpies, + }); + + wrapper = shallowMountExtended(FiltersTemplate, { + store, + slots: { + default: '<p>Filters Content</p>', + }, + }); + }; + + const findForm = () => wrapper.findComponent(GlForm); + const findDividers = () => wrapper.findAll('hr'); + const findApplyButton = () => wrapper.findComponent(GlButton); + const findResetButton = () => wrapper.findComponent(GlLink); + const findSlotContent = () => wrapper.findByText('Filters Content'); + + describe('Renders correctly', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders form', () => { + expect(findForm().exists()).toBe(true); + }); + + it('renders dividers', () => { + expect(findDividers()).toHaveLength(2); + }); + + it('renders slot content', () => { + expect(findSlotContent().exists()).toBe(true); + }); + + it('renders ApplyButton', () => { + expect(findApplyButton().exists()).toBe(true); + }); + + it('renders reset button', () => { + expect(findResetButton().exists()).toBe(false); + }); + }); + + describe('resetButton', () => { + describe.each` + description | sidebarDirty | queryLangFilters | exists + ${'sidebar dirty only'} | ${true} | ${[]} | ${true} + ${'query filters only'} | ${false} | ${['JSON', 'C']} | ${false} + ${'sidebar dirty and query filters'} | ${true} | ${['JSON', 'C']} | ${true} + ${'sidebar not dirty and no query filters'} | ${false} | ${[]} | ${false} + `('$description', ({ sidebarDirty, queryLangFilters, exists }) => { + beforeEach(() => { + getterSpies.queryLanguageFilters = jest.fn(() => queryLangFilters); + + const query = { + ...MOCK_QUERY, + language: queryLangFilters, + state: undefined, + labels: undefined, + confidential: undefined, + }; + + createComponent({ + sidebarDirty, + query, + urlQuery: query, + }); + }); + + it(`button is ${exists ? 'shown' : 'hidden'}`, () => { + expect(findResetButton().exists()).toBe(exists); + }); + }); + }); + + describe('ApplyButton', () => { + describe('when sidebarDirty is false', () => { + beforeEach(() => { + createComponent({ sidebarDirty: false }); + }); + + it('disables the button', () => { + expect(findApplyButton().attributes('disabled')).toBeDefined(); + }); + }); + + describe('when sidebarDirty is true', () => { + beforeEach(() => { + createComponent({ sidebarDirty: true }); + }); + + it('enables the button', () => { + expect(findApplyButton().attributes('disabled')).toBe(undefined); + }); + }); + }); + + describe('actions', () => { + beforeEach(() => { + createComponent({ sidebarDirty: true }); + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + }); + + afterEach(() => { + unmockTracking(); + }); + + it('clicking ApplyButton calls applyQuery', () => { + findForm().vm.$emit('submit', { preventDefault: () => {} }); + + expect(actionSpies.applyQuery).toHaveBeenCalled(); + expect(trackingSpy).toHaveBeenCalledWith(TRACKING_ACTION_CLICK, TRACKING_LABEL_APPLY, { + label: getterSpies.currentScope(), + }); + }); + + it('clicking resetButton calls resetQuery', () => { + findResetButton().vm.$emit('click'); + + expect(actionSpies.resetQuery).toHaveBeenCalled(); + expect(trackingSpy).toHaveBeenCalledWith(TRACKING_ACTION_CLICK, TRACKING_LABEL_RESET, { + label: getterSpies.currentScope(), + }); + }); + }); +}); diff --git a/spec/frontend/search/sidebar/components/issues_filters_spec.js b/spec/frontend/search/sidebar/components/issues_filters_spec.js new file mode 100644 index 00000000000..cab3a78bd34 --- /dev/null +++ b/spec/frontend/search/sidebar/components/issues_filters_spec.js @@ -0,0 +1,106 @@ +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; +import Vuex from 'vuex'; +import { MOCK_QUERY } from 'jest/search/mock_data'; +import IssuesFilters from '~/search/sidebar/components/issues_filters.vue'; +import ConfidentialityFilter from '~/search/sidebar/components/confidentiality_filter/index.vue'; +import StatusFilter from '~/search/sidebar/components/status_filter/index.vue'; +import LabelFilter from '~/search/sidebar/components/label_filter/index.vue'; + +Vue.use(Vuex); + +describe('GlobalSearch IssuesFilters', () => { + let wrapper; + + const defaultGetters = { + currentScope: () => 'issues', + }; + + const createComponent = (initialState, ff = true) => { + const store = new Vuex.Store({ + state: { + urlQuery: MOCK_QUERY, + ...initialState, + }, + getters: defaultGetters, + }); + + wrapper = shallowMount(IssuesFilters, { + store, + provide: { + glFeatures: { + searchIssueLabelAggregation: ff, + }, + }, + }); + }; + + const findStatusFilter = () => wrapper.findComponent(StatusFilter); + const findConfidentialityFilter = () => wrapper.findComponent(ConfidentialityFilter); + const findLabelFilter = () => wrapper.findComponent(LabelFilter); + const findDividers = () => wrapper.findAll('hr'); + + describe('Renders correctly with FF enabled', () => { + beforeEach(() => { + createComponent({ urlQuery: MOCK_QUERY }); + }); + it('renders StatusFilter', () => { + expect(findStatusFilter().exists()).toBe(true); + }); + + it('renders ConfidentialityFilter', () => { + expect(findConfidentialityFilter().exists()).toBe(true); + }); + + it('renders LabelFilter', () => { + expect(findLabelFilter().exists()).toBe(true); + }); + + it('renders dividers correctly', () => { + expect(findDividers()).toHaveLength(2); + }); + }); + + describe('Renders correctly with FF disabled', () => { + beforeEach(() => { + createComponent({ urlQuery: MOCK_QUERY }, false); + }); + it('renders StatusFilter', () => { + expect(findStatusFilter().exists()).toBe(true); + }); + + it('renders ConfidentialityFilter', () => { + expect(findConfidentialityFilter().exists()).toBe(true); + }); + + it("doesn't render LabelFilter", () => { + expect(findLabelFilter().exists()).toBe(false); + }); + + it('renders divider correctly', () => { + expect(findDividers()).toHaveLength(1); + }); + }); + + describe('Renders correctly with wrong scope', () => { + beforeEach(() => { + defaultGetters.currentScope = () => 'blobs'; + createComponent({ urlQuery: MOCK_QUERY }); + }); + it("doesn't render StatusFilter", () => { + expect(findStatusFilter().exists()).toBe(false); + }); + + it("doesn't render ConfidentialityFilter", () => { + expect(findConfidentialityFilter().exists()).toBe(false); + }); + + it("doesn't render LabelFilter", () => { + expect(findLabelFilter().exists()).toBe(false); + }); + + it("doesn't render dividers", () => { + expect(findDividers()).toHaveLength(0); + }); + }); +}); diff --git a/spec/frontend/search/sidebar/components/language_filter_spec.js b/spec/frontend/search/sidebar/components/language_filter_spec.js index 817199d7cfe..88be8f908d7 100644 --- a/spec/frontend/search/sidebar/components/language_filter_spec.js +++ b/spec/frontend/search/sidebar/components/language_filter_spec.js @@ -1,4 +1,4 @@ -import { GlAlert, GlFormCheckbox, GlForm } from '@gitlab/ui'; +import { GlAlert, GlFormCheckbox } from '@gitlab/ui'; import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; @@ -13,13 +13,11 @@ import CheckboxFilter from '~/search/sidebar/components/language_filter/checkbox import { TRACKING_LABEL_SHOW_MORE, - TRACKING_CATEGORY, TRACKING_PROPERTY_MAX, TRACKING_LABEL_MAX, TRACKING_LABEL_FILTERS, TRACKING_ACTION_SHOW, TRACKING_ACTION_CLICK, - TRACKING_LABEL_APPLY, TRACKING_LABEL_ALL, } from '~/search/sidebar/components/language_filter/tracking'; @@ -61,10 +59,7 @@ describe('GlobalSearchSidebarLanguageFilter', () => { }); }; - const findForm = () => wrapper.findComponent(GlForm); const findCheckboxFilter = () => wrapper.findComponent(CheckboxFilter); - const findApplyButton = () => wrapper.findByTestId('apply-button'); - const findResetButton = () => wrapper.findByTestId('reset-button'); const findShowMoreButton = () => wrapper.findByTestId('show-more-button'); const findAlert = () => wrapper.findComponent(GlAlert); const findAllCheckboxes = () => wrapper.findAllComponents(GlFormCheckbox); @@ -80,10 +75,6 @@ describe('GlobalSearchSidebarLanguageFilter', () => { unmockTracking(); }); - it('renders form', () => { - expect(findForm().exists()).toBe(true); - }); - it('renders checkbox-filter', () => { expect(findCheckboxFilter().exists()).toBe(true); }); @@ -93,10 +84,6 @@ describe('GlobalSearchSidebarLanguageFilter', () => { expect(findAllCheckboxes()).toHaveLength(10); }); - it('renders ApplyButton', () => { - expect(findApplyButton().exists()).toBe(true); - }); - it('renders Show More button', () => { expect(findShowMoreButton().exists()).toBe(true); }); @@ -106,47 +93,6 @@ describe('GlobalSearchSidebarLanguageFilter', () => { }); }); - describe('resetButton', () => { - describe.each` - description | sidebarDirty | queryFilters | exists - ${'sidebar dirty only'} | ${true} | ${[]} | ${false} - ${'query filters only'} | ${false} | ${['JSON', 'C']} | ${false} - ${'sidebar dirty and query filters'} | ${true} | ${['JSON', 'C']} | ${true} - ${'no sidebar and no query filters'} | ${false} | ${[]} | ${false} - `('$description', ({ sidebarDirty, queryFilters, exists }) => { - beforeEach(() => { - getterSpies.queryLanguageFilters = jest.fn(() => queryFilters); - createComponent({ sidebarDirty, query: { ...MOCK_QUERY, language: queryFilters } }); - }); - - it(`button is ${exists ? 'shown' : 'hidden'}`, () => { - expect(findResetButton().exists()).toBe(exists); - }); - }); - }); - - describe('ApplyButton', () => { - describe('when sidebarDirty is false', () => { - beforeEach(() => { - createComponent({ sidebarDirty: false }); - }); - - it('disables the button', () => { - expect(findApplyButton().attributes('disabled')).toBeDefined(); - }); - }); - - describe('when sidebarDirty is true', () => { - beforeEach(() => { - createComponent({ sidebarDirty: true }); - }); - - it('enables the button', () => { - expect(findApplyButton().attributes('disabled')).toBe(undefined); - }); - }); - }); - describe('Show All button works', () => { beforeEach(() => { createComponent(); @@ -211,19 +157,5 @@ describe('GlobalSearchSidebarLanguageFilter', () => { it('uses action fetchAllAggregation', () => { expect(actionSpies.fetchAllAggregation).toHaveBeenCalled(); }); - - it('clicking ApplyButton calls applyQuery', () => { - findForm().vm.$emit('submit', { preventDefault: () => {} }); - - expect(actionSpies.applyQuery).toHaveBeenCalled(); - }); - - it('sends tracking information clicking ApplyButton', () => { - findForm().vm.$emit('submit', { preventDefault: () => {} }); - - expect(trackingSpy).toHaveBeenCalledWith(TRACKING_ACTION_CLICK, TRACKING_LABEL_APPLY, { - label: TRACKING_CATEGORY, - }); - }); }); }); diff --git a/spec/frontend/search/sidebar/components/merge_requests_filters_spec.js b/spec/frontend/search/sidebar/components/merge_requests_filters_spec.js new file mode 100644 index 00000000000..0932f8e47d2 --- /dev/null +++ b/spec/frontend/search/sidebar/components/merge_requests_filters_spec.js @@ -0,0 +1,28 @@ +import { shallowMount } from '@vue/test-utils'; +import MergeRequestsFilters from '~/search/sidebar/components/merge_requests_filters.vue'; +import StatusFilter from '~/search/sidebar/components/status_filter/index.vue'; +import FiltersTemplate from '~/search/sidebar/components/filters_template.vue'; + +describe('GlobalSearch MergeRequestsFilters', () => { + let wrapper; + + const findStatusFilter = () => wrapper.findComponent(StatusFilter); + const findFiltersTemplate = () => wrapper.findComponent(FiltersTemplate); + + const createComponent = () => { + wrapper = shallowMount(MergeRequestsFilters); + }; + + describe('Renders correctly', () => { + beforeEach(() => { + createComponent(); + }); + it('renders ConfidentialityFilter', () => { + expect(findStatusFilter().exists()).toBe(true); + }); + + it('renders FiltersTemplate', () => { + expect(findFiltersTemplate().exists()).toBe(true); + }); + }); +}); diff --git a/spec/frontend/search/store/actions_spec.js b/spec/frontend/search/store/actions_spec.js index 2051e731647..a6ec7a06cfb 100644 --- a/spec/frontend/search/store/actions_spec.js +++ b/spec/frontend/search/store/actions_spec.js @@ -327,28 +327,6 @@ describe('Global Search Store Actions', () => { }); }); - describe('resetLanguageQueryWithRedirect', () => { - it('calls visitUrl and setParams with the state.query', () => { - return testAction(actions.resetLanguageQueryWithRedirect, null, state, [], [], () => { - expect(urlUtils.setUrlParams).toHaveBeenCalledWith({ ...state.query, page: null }); - expect(urlUtils.visitUrl).toHaveBeenCalled(); - }); - }); - }); - - describe('resetLanguageQuery', () => { - it('calls commit SET_QUERY with value []', () => { - state = { ...state, query: { ...state.query, language: ['YAML', 'Text', 'Markdown'] } }; - return testAction( - actions.resetLanguageQuery, - null, - state, - [{ type: types.SET_QUERY, payload: { key: 'language', value: [] } }], - [], - ); - }); - }); - describe('closeLabel', () => { beforeEach(() => { state = createState({ diff --git a/spec/frontend/search/store/getters_spec.js b/spec/frontend/search/store/getters_spec.js index 772acb39a57..66a888037ea 100644 --- a/spec/frontend/search/store/getters_spec.js +++ b/spec/frontend/search/store/getters_spec.js @@ -70,18 +70,6 @@ describe('Global Search Store Getters', () => { }); }); - describe('currentUrlQueryHasLanguageFilters', () => { - it.each` - description | lang | result - ${'has valid language'} | ${{ language: ['a', 'b'] }} | ${true} - ${'has empty lang'} | ${{ language: [] }} | ${false} - ${'has no lang'} | ${{}} | ${false} - `('$description', ({ lang, result }) => { - state.urlQuery = lang; - expect(getters.currentUrlQueryHasLanguageFilters(state)).toBe(result); - }); - }); - describe('navigationItems', () => { it('returns the re-mapped navigation data', () => { state.navigation = MOCK_NAVIGATION; diff --git a/spec/frontend/super_sidebar/components/context_header_spec.js b/spec/frontend/super_sidebar/components/context_header_spec.js new file mode 100644 index 00000000000..943b659c997 --- /dev/null +++ b/spec/frontend/super_sidebar/components/context_header_spec.js @@ -0,0 +1,50 @@ +import { GlAvatar } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ContextHeader from '~/super_sidebar/components/context_header.vue'; + +describe('ContextHeader component', () => { + let wrapper; + + const context = { + id: 1, + title: 'Title', + avatar: '/path/to/avatar.png', + }; + + const findGlAvatar = () => wrapper.getComponent(GlAvatar); + + const createWrapper = (props = {}) => { + wrapper = shallowMountExtended(ContextHeader, { + propsData: { + context, + expanded: false, + ...props, + }, + }); + }; + + describe('with an avatar', () => { + it('passes the correct props to GlAvatar', () => { + createWrapper(); + const avatar = findGlAvatar(); + + expect(avatar.props('shape')).toBe('rect'); + expect(avatar.props('entityName')).toBe(context.title); + expect(avatar.props('entityId')).toBe(context.id); + expect(avatar.props('src')).toBe(context.avatar); + }); + + it('renders the avatar with a custom shape', () => { + const customShape = 'circle'; + createWrapper({ + context: { + ...context, + avatar_shape: customShape, + }, + }); + const avatar = findGlAvatar(); + + expect(avatar.props('shape')).toBe(customShape); + }); + }); +}); diff --git a/spec/frontend/super_sidebar/components/context_switcher_toggle_spec.js b/spec/frontend/super_sidebar/components/context_switcher_toggle_spec.js index 7172b60d0fa..c20d3c2745f 100644 --- a/spec/frontend/super_sidebar/components/context_switcher_toggle_spec.js +++ b/spec/frontend/super_sidebar/components/context_switcher_toggle_spec.js @@ -1,4 +1,4 @@ -import { GlAvatar } from '@gitlab/ui'; +import { GlIcon } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import ContextSwitcherToggle from '~/super_sidebar/components/context_switcher_toggle.vue'; @@ -11,7 +11,7 @@ describe('ContextSwitcherToggle component', () => { avatar: '/path/to/avatar.png', }; - const findGlAvatar = () => wrapper.getComponent(GlAvatar); + const findGlIcon = () => wrapper.getComponent(GlIcon); const createWrapper = (props = {}) => { wrapper = shallowMountExtended(ContextSwitcherToggle, { @@ -23,28 +23,17 @@ describe('ContextSwitcherToggle component', () => { }); }; - describe('with an avatar', () => { - it('passes the correct props to GlAvatar', () => { - createWrapper(); - const avatar = findGlAvatar(); + it('renders "chevron-down" icon when not expanded', () => { + createWrapper(); - expect(avatar.props('shape')).toBe('rect'); - expect(avatar.props('entityName')).toBe(context.title); - expect(avatar.props('entityId')).toBe(context.id); - expect(avatar.props('src')).toBe(context.avatar); - }); + expect(findGlIcon().props('name')).toBe('chevron-down'); + }); - it('renders the avatar with a custom shape', () => { - const customShape = 'circle'; - createWrapper({ - context: { - ...context, - avatar_shape: customShape, - }, - }); - const avatar = findGlAvatar(); - - expect(avatar.props('shape')).toBe(customShape); + it('renders "chevron-up" icon when expanded', () => { + createWrapper({ + expanded: true, }); + + expect(findGlIcon().props('name')).toBe('chevron-up'); }); }); diff --git a/spec/frontend/super_sidebar/components/sidebar_menu_spec.js b/spec/frontend/super_sidebar/components/sidebar_menu_spec.js index ac94f3f8f82..5d9a35fbf70 100644 --- a/spec/frontend/super_sidebar/components/sidebar_menu_spec.js +++ b/spec/frontend/super_sidebar/components/sidebar_menu_spec.js @@ -25,6 +25,7 @@ describe('Sidebar Menu', () => { }, propsData: { items: sidebarData.current_menu_items, + isLoggedIn: sidebarData.is_logged_in, pinnedItemIds: sidebarData.pinned_items, panelType: sidebarData.panel_type, updatePinsUrl: sidebarData.update_pins_url, diff --git a/spec/frontend/super_sidebar/components/sidebar_peek_behavior_spec.js b/spec/frontend/super_sidebar/components/sidebar_peek_behavior_spec.js index abd9c1dc44d..94ef072a951 100644 --- a/spec/frontend/super_sidebar/components/sidebar_peek_behavior_spec.js +++ b/spec/frontend/super_sidebar/components/sidebar_peek_behavior_spec.js @@ -35,8 +35,10 @@ describe('SidebarPeek component', () => { let wrapper; let trackingSpy = null; - const createComponent = () => { - wrapper = mount(SidebarPeek); + const createComponent = (props = { isMouseOverSidebar: false }) => { + wrapper = mount(SidebarPeek, { + propsData: props, + }); }; const moveMouse = (clientX) => { @@ -163,6 +165,17 @@ describe('SidebarPeek component', () => { expect(lastNChangeEvents(2)).toEqual([STATE_OPEN, STATE_CLOSED]); }); + it('does not transition to will-close or closed when mouse is over sidebar child element', () => { + createComponent({ isMouseOverSidebar: true }); + moveMouse(0); + jest.runOnlyPendingTimers(); + + moveMouse(X_SIDEBAR_EDGE); + moveMouse(X_AWAY_FROM_SIDEBAR); + + expect(lastNChangeEvents(1)).toEqual([STATE_OPEN]); + }); + it('immediately transitions will-close -> closed if mouse moves far away', () => { moveMouse(0); jest.runOnlyPendingTimers(); diff --git a/spec/frontend/super_sidebar/components/super_sidebar_spec.js b/spec/frontend/super_sidebar/components/super_sidebar_spec.js index 0c785109b5e..7b7b8a7be13 100644 --- a/spec/frontend/super_sidebar/components/super_sidebar_spec.js +++ b/spec/frontend/super_sidebar/components/super_sidebar_spec.js @@ -11,6 +11,7 @@ import SidebarPeekBehavior, { STATE_WILL_CLOSE, } from '~/super_sidebar/components/sidebar_peek_behavior.vue'; import SidebarPortalTarget from '~/super_sidebar/components/sidebar_portal_target.vue'; +import ContextHeader from '~/super_sidebar/components/context_header.vue'; import ContextSwitcher from '~/super_sidebar/components/context_switcher.vue'; import SidebarMenu from '~/super_sidebar/components/sidebar_menu.vue'; import { sidebarState } from '~/super_sidebar/constants'; @@ -20,7 +21,7 @@ import { } from '~/super_sidebar/super_sidebar_collapsed_state_manager'; import { stubComponent } from 'helpers/stub_component'; import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; -import { sidebarData as mockSidebarData } from '../mock_data'; +import { sidebarData as mockSidebarData, loggedOutSidebarData } from '../mock_data'; const initialSidebarState = { ...sidebarState }; @@ -42,6 +43,7 @@ describe('SuperSidebar component', () => { const findSidebar = () => wrapper.findByTestId('super-sidebar'); const findUserBar = () => wrapper.findComponent(UserBar); + const findContextHeader = () => wrapper.findComponent(ContextHeader); const findContextSwitcher = () => wrapper.findComponent(ContextSwitcher); const findNavContainer = () => wrapper.findByTestId('nav-container'); const findHelpCenter = () => wrapper.findComponent(HelpCenter); @@ -230,6 +232,15 @@ describe('SuperSidebar component', () => { expect(findSidebar().classes()).not.toContain(peekHintClass); }, ); + + it('keeps track of if sidebar has mouseover or not', async () => { + createWrapper({ sidebarState: { isCollapsed: false, isPeekable: true } }); + expect(findPeekBehavior().props('isMouseOverSidebar')).toBe(false); + await findSidebar().trigger('mouseenter'); + expect(findPeekBehavior().props('isMouseOverSidebar')).toBe(true); + await findSidebar().trigger('mouseleave'); + expect(findPeekBehavior().props('isMouseOverSidebar')).toBe(false); + }); }); describe('nav container', () => { @@ -259,4 +270,15 @@ describe('SuperSidebar component', () => { expect(findTrialStatusPopover().exists()).toBe(true); }); }); + + describe('Logged out', () => { + beforeEach(() => { + createWrapper({ sidebarData: loggedOutSidebarData }); + }); + + it('renders context header instead of context switcher', () => { + expect(findContextHeader().exists()).toBe(true); + expect(findContextSwitcher().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/super_sidebar/components/user_bar_spec.js b/spec/frontend/super_sidebar/components/user_bar_spec.js index 272e0237219..d90553dfdd3 100644 --- a/spec/frontend/super_sidebar/components/user_bar_spec.js +++ b/spec/frontend/super_sidebar/components/user_bar_spec.js @@ -4,6 +4,7 @@ import Vue, { nextTick } from 'vue'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { __ } from '~/locale'; import CreateMenu from '~/super_sidebar/components/create_menu.vue'; +import UserMenu from '~/super_sidebar/components/user_menu.vue'; import SearchModal from '~/super_sidebar/components/global_search/components/global_search.vue'; import BrandLogo from 'jh_else_ce/super_sidebar/components/brand_logo.vue'; import MergeRequestMenu from '~/super_sidebar/components/merge_request_menu.vue'; @@ -11,13 +12,14 @@ import UserBar from '~/super_sidebar/components/user_bar.vue'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import waitForPromises from 'helpers/wait_for_promises'; import { userCounts } from '~/super_sidebar/user_counts_manager'; -import { sidebarData } from '../mock_data'; +import { sidebarData as mockSidebarData, loggedOutSidebarData } from '../mock_data'; import { MOCK_DEFAULT_SEARCH_OPTIONS } from './global_search/mock_data'; describe('UserBar component', () => { let wrapper; const findCreateMenu = () => wrapper.findComponent(CreateMenu); + const findUserMenu = () => wrapper.findComponent(UserMenu); const findIssuesCounter = () => wrapper.findByTestId('issues-shortcut-button'); const findMRsCounter = () => wrapper.findByTestId('merge-requests-shortcut-button'); const findTodosCounter = () => wrapper.findByTestId('todos-shortcut-button'); @@ -37,13 +39,13 @@ describe('UserBar component', () => { }); const createWrapper = ({ hasCollapseButton = true, - extraSidebarData = {}, + sidebarData = mockSidebarData, provideOverrides = {}, } = {}) => { wrapper = shallowMountExtended(UserBar, { propsData: { hasCollapseButton, - sidebarData: { ...sidebarData, ...extraSidebarData }, + sidebarData, }, provide: { toggleNewNavEndpoint: '/-/profile/preferences', @@ -63,17 +65,17 @@ describe('UserBar component', () => { }); it('passes the "Create new..." menu groups to the create-menu component', () => { - expect(findCreateMenu().props('groups')).toBe(sidebarData.create_new_menu_groups); + expect(findCreateMenu().props('groups')).toBe(mockSidebarData.create_new_menu_groups); }); it('passes the "Merge request" menu groups to the merge_request_menu component', () => { - expect(findMergeRequestMenu().props('items')).toBe(sidebarData.merge_request_menu); + expect(findMergeRequestMenu().props('items')).toBe(mockSidebarData.merge_request_menu); }); it('renders issues counter', () => { const isuesCounter = findIssuesCounter(); expect(isuesCounter.props('count')).toBe(userCounts.assigned_issues); - expect(isuesCounter.props('href')).toBe(sidebarData.issues_dashboard_path); + expect(isuesCounter.props('href')).toBe(mockSidebarData.issues_dashboard_path); expect(isuesCounter.props('label')).toBe(__('Issues')); expect(isuesCounter.attributes('data-track-action')).toBe('click_link'); expect(isuesCounter.attributes('data-track-label')).toBe('issues_link'); @@ -95,7 +97,7 @@ describe('UserBar component', () => { describe('Todos counter', () => { it('renders it', () => { const todosCounter = findTodosCounter(); - expect(todosCounter.props('href')).toBe(sidebarData.todos_dashboard_path); + expect(todosCounter.props('href')).toBe(mockSidebarData.todos_dashboard_path); expect(todosCounter.props('label')).toBe(__('To-Do list')); expect(todosCounter.attributes('data-track-action')).toBe('click_link'); expect(todosCounter.attributes('data-track-label')).toBe('todos_link'); @@ -114,7 +116,7 @@ describe('UserBar component', () => { it('renders branding logo', () => { expect(findBrandLogo().exists()).toBe(true); - expect(findBrandLogo().props('logoUrl')).toBe(sidebarData.logo_url); + expect(findBrandLogo().props('logoUrl')).toBe(mockSidebarData.logo_url); }); it('does not render the "Stop impersonating" button', () => { @@ -134,16 +136,16 @@ describe('UserBar component', () => { describe('GitLab Next badge', () => { describe('when on canary', () => { it('should render a badge to switch off GitLab Next', () => { - createWrapper({ extraSidebarData: { gitlab_com_and_canary: true } }); + createWrapper({ sidebarData: { ...mockSidebarData, gitlab_com_and_canary: true } }); const badge = wrapper.findComponent(GlBadge); expect(badge.text()).toBe('Next'); - expect(badge.attributes('href')).toBe(sidebarData.canary_toggle_com_url); + expect(badge.attributes('href')).toBe(mockSidebarData.canary_toggle_com_url); }); }); describe('when not on canary', () => { it('should not render the GitLab Next badge', () => { - createWrapper({ extraSidebarData: { gitlab_com_and_canary: false } }); + createWrapper({ sidebarData: { ...mockSidebarData, gitlab_com_and_canary: false } }); const badge = wrapper.findComponent(GlBadge); expect(badge.exists()).toBe(false); }); @@ -206,8 +208,36 @@ describe('UserBar component', () => { it('sets the href and data-method attributes', () => { const btn = findStopImpersonationButton(); - expect(btn.attributes('href')).toBe(sidebarData.stop_impersonation_path); + expect(btn.attributes('href')).toBe(mockSidebarData.stop_impersonation_path); expect(btn.attributes('data-method')).toBe('delete'); }); }); + + describe('Logged out', () => { + beforeEach(() => { + createWrapper({ sidebarData: loggedOutSidebarData, gitlab_com_and_canary: true }); + }); + + it('does not render brand logo', () => { + expect(findBrandLogo().exists()).toBe(false); + }); + + it('does not render Next badge', () => { + expect(wrapper.findComponent(GlBadge).exists()).toBe(false); + }); + + it('does not render create menu', () => { + expect(findCreateMenu().exists()).toBe(false); + }); + + it('does not render user menu', () => { + expect(findUserMenu().exists()).toBe(false); + }); + + it('does not render counters', () => { + expect(findIssuesCounter().exists()).toBe(false); + expect(findMRsCounter().exists()).toBe(false); + expect(findTodosCounter().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/super_sidebar/mock_data.js b/spec/frontend/super_sidebar/mock_data.js index 72c67e34038..df45360a898 100644 --- a/spec/frontend/super_sidebar/mock_data.js +++ b/spec/frontend/super_sidebar/mock_data.js @@ -72,6 +72,7 @@ export const mergeRequestMenuGroup = [ ]; export const sidebarData = { + is_logged_in: true, current_menu_items: [], current_context_header: { title: 'Your Work', @@ -120,6 +121,26 @@ export const sidebarData = { ], }; +export const loggedOutSidebarData = { + is_logged_in: false, + current_menu_items: [], + current_context_header: { + title: 'Your Work', + icon: 'work', + }, + support_path: '/support', + display_whats_new: true, + whats_new_most_recent_release_items_count: 5, + whats_new_version_digest: 1, + show_version_check: false, + gitlab_version: { major: 16, minor: 0 }, + gitlab_version_check: { severity: 'success' }, + search: { + search_path: '/search', + }, + panel_type: 'your_work', +}; + export const userMenuMockStatus = { can_update: false, busy: false, diff --git a/spec/helpers/application_settings_helper_spec.rb b/spec/helpers/application_settings_helper_spec.rb index f924704ab54..9d591164547 100644 --- a/spec/helpers/application_settings_helper_spec.rb +++ b/spec/helpers/application_settings_helper_spec.rb @@ -74,6 +74,10 @@ RSpec.describe ApplicationSettingsHelper do expect(helper.visible_attributes).to include(*params) end + it 'contains :namespace_aggregation_schedule_lease_duration_in_seconds' do + expect(helper.visible_attributes).to include(:namespace_aggregation_schedule_lease_duration_in_seconds) + end + context 'when on SaaS', :saas do it 'does not contain :deactivate_dormant_users' do expect(helper.visible_attributes).not_to include(:deactivate_dormant_users) diff --git a/spec/helpers/nav_helper_spec.rb b/spec/helpers/nav_helper_spec.rb index 4a02b184522..4b83561b265 100644 --- a/spec/helpers/nav_helper_spec.rb +++ b/spec/helpers/nav_helper_spec.rb @@ -169,15 +169,39 @@ RSpec.describe NavHelper, feature_category: :navigation do end end - context 'when nil is provided' do - specify { expect(helper.show_super_sidebar?(nil)).to eq false } + shared_examples 'anonymous show_super_sidebar is supposed to' do + before do + stub_feature_flags(super_sidebar_logged_out: feature_flag) + end + + context 'when super_sidebar_logged_out feature flag is disabled' do + let(:feature_flag) { false } + + specify { expect(subject).to eq false } + end + + context 'when super_sidebar_logged_out feature flag is enabled' do + let(:feature_flag) { true } + + specify { expect(subject).to eq true } + end end - context 'when no user is signed-in' do - specify do - allow(helper).to receive(:current_user).and_return(nil) + context 'without a user' do + context 'with current_user (nil) as a default' do + before do + allow(helper).to receive(:current_user).and_return(nil) + end + + subject { helper.show_super_sidebar? } + + it_behaves_like 'anonymous show_super_sidebar is supposed to' + end + + context 'with nil provided as an argument' do + subject { helper.show_super_sidebar?(nil) } - expect(helper.show_super_sidebar?).to eq false + it_behaves_like 'anonymous show_super_sidebar is supposed to' end end diff --git a/spec/helpers/sidebars_helper_spec.rb b/spec/helpers/sidebars_helper_spec.rb index 8d8bbcd2737..1a81fec3a0b 100644 --- a/spec/helpers/sidebars_helper_spec.rb +++ b/spec/helpers/sidebars_helper_spec.rb @@ -91,10 +91,16 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do allow(user).to receive(:pinned_nav_items).and_return({ panel_type => %w[foo bar], 'another_panel' => %w[baz] }) end + # Tests for logged-out sidebar context + it_behaves_like 'logged-out super-sidebar context' + + # Tests for logged-in sidebar context below + it_behaves_like 'shared super sidebar context' + it { is_expected.to include({ is_logged_in: true }) } + it 'returns sidebar values from user', :use_clean_rails_memory_store_caching do expect(subject).to include({ - current_context_header: nil, - current_menu_items: nil, + is_logged_in: true, name: user.name, username: user.username, avatar_url: user.avatar_url, @@ -128,25 +134,10 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do todos_dashboard_path: dashboard_todos_path, projects_path: dashboard_projects_path, groups_path: dashboard_groups_path, - support_path: helper.support_url, - display_whats_new: helper.display_whats_new?, - whats_new_most_recent_release_items_count: helper.whats_new_most_recent_release_items_count, - whats_new_version_digest: helper.whats_new_version_digest, - show_version_check: helper.show_version_check?, - gitlab_version: Gitlab.version_info, - gitlab_version_check: helper.gitlab_version_check, gitlab_com_but_not_canary: Gitlab.com_but_not_canary?, gitlab_com_and_canary: Gitlab.com_and_canary?, canary_toggle_com_url: Gitlab::Saas.canary_toggle_com_url, - search: { - search_path: search_path, - issues_path: issues_dashboard_path, - mr_path: merge_requests_dashboard_path, - autocomplete_path: search_autocomplete_path, - search_context: helper.header_search_context - }, pinned_items: %w[foo bar], - panel_type: panel_type, update_pins_url: pins_url, shortcut_links: [ { @@ -471,8 +462,11 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do end describe 'when impersonating' do + before do + session[:impersonator_id] = 5 + end + it 'sets is_impersonating to `true`' do - expect(helper).to receive(:session).and_return({ impersonator_id: 1 }) expect(subject[:is_impersonating]).to be(true) end end diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index dfaba969153..6179c36b0ea 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -81,6 +81,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu expect(json_response['ci_max_includes']).to eq(150) expect(json_response['allow_account_deletion']).to eq(true) expect(json_response['gitlab_shell_operation_limit']).to eq(600) + expect(json_response['namespace_aggregation_schedule_lease_duration_in_seconds']).to eq(300) end end @@ -193,7 +194,8 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu silent_mode_enabled: true, valid_runner_registrars: ['group'], allow_account_deletion: false, - gitlab_shell_operation_limit: 500 + gitlab_shell_operation_limit: 500, + namespace_aggregation_schedule_lease_duration_in_seconds: 400 } expect(response).to have_gitlab_http_status(:ok) @@ -270,6 +272,7 @@ RSpec.describe API::Settings, 'Settings', :do_not_mock_admin_mode_setting, featu expect(json_response['valid_runner_registrars']).to eq(['group']) expect(json_response['allow_account_deletion']).to be(false) expect(json_response['gitlab_shell_operation_limit']).to be(500) + expect(json_response['namespace_aggregation_schedule_lease_duration_in_seconds']).to be(400) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4d66784d943..651581ba270 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -295,8 +295,14 @@ RSpec.configure do |config| # Only a few percent of users will be "enrolled" into the new nav with this flag. # Having it enabled globally would make it impossible to test the current nav. + # https://gitlab.com/gitlab-org/gitlab/-/issues/420121 stub_feature_flags(super_sidebar_nav_enrolled: false) + # The anonymous super-sidebar is under heavy development and enabling the flag + # globally leads to a lot of errors. This issue is for fixing all test to work with the + # new nav: https://gitlab.com/gitlab-org/gitlab/-/issues/420119 + stub_feature_flags(super_sidebar_logged_out: false) + # It's disabled in specs because we don't support certain features which # cause spec failures. stub_feature_flags(gitlab_error_tracking: false) diff --git a/spec/support/shared_examples/helpers/super_sidebar_shared_examples.rb b/spec/support/shared_examples/helpers/super_sidebar_shared_examples.rb new file mode 100644 index 00000000000..636b9870bd7 --- /dev/null +++ b/spec/support/shared_examples/helpers/super_sidebar_shared_examples.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'shared super sidebar context' do + it 'returns sidebar values for logged-in users and logged-out users', :use_clean_rails_memory_store_caching do + expect(subject).to include({ + current_menu_items: nil, + current_context_header: nil, + support_path: helper.support_url, + display_whats_new: helper.display_whats_new?, + whats_new_most_recent_release_items_count: helper.whats_new_most_recent_release_items_count, + whats_new_version_digest: helper.whats_new_version_digest, + show_version_check: helper.show_version_check?, + gitlab_version: Gitlab.version_info, + gitlab_version_check: helper.gitlab_version_check, + search: { + search_path: search_path, + issues_path: issues_dashboard_path, + mr_path: merge_requests_dashboard_path, + autocomplete_path: search_autocomplete_path, + search_context: helper.header_search_context + }, + panel_type: panel_type + }) + end +end + +RSpec.shared_examples 'logged-out super-sidebar context' do + subject do + helper.super_sidebar_context(nil, group: nil, project: nil, panel: panel, panel_type: panel_type) + end + + it_behaves_like 'shared super sidebar context' + + it { is_expected.to include({ is_logged_in: false }) } +end |