From 3ae055ae56408f2d7a96019aa3bfa395eb142a9e Mon Sep 17 00:00:00 2001 From: Olena Horal-Koretska <ohoralkoretska@gitlab.com> Date: Wed, 21 Jun 2023 10:03:09 +0000 Subject: [PATCH] Command palette jump to project file --- .../command_palette/command_palette_items.vue | 55 ++++++++++++-- .../command_palette/constants.js | 7 +- .../command_palette/fake_search_input.vue | 4 +- .../global_search/command_palette/utils.js | 14 +++- .../components/global_search.vue | 27 +++++-- .../super_sidebar/super_sidebar_bundle.js | 14 +++- app/helpers/sidebars_helper.rb | 10 +++ app/views/layouts/_page.html.haml | 2 +- locale/gitlab.pot | 8 +- .../command_palette_items_spec.js | 73 ++++++++++++++++++- .../command_palette/mock_data.js | 12 +++ .../command_palette/utils_spec.js | 13 ++++ .../components/global_search_spec.js | 10 ++- spec/helpers/sidebars_helper_spec.rb | 27 +++++++ 14 files changed, 246 insertions(+), 30 deletions(-) diff --git a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_palette_items.vue b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_palette_items.vue index 96e6c9bab9ef..b8921bd0bfa4 100644 --- a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_palette_items.vue +++ b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/command_palette_items.vue @@ -5,18 +5,20 @@ import { GlDisclosureDropdownGroup, GlLoadingIcon } from '@gitlab/ui'; import axios from '~/lib/utils/axios_utils'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import { getFormattedItem } from '../utils'; + import { COMMON_HANDLES, COMMAND_HANDLE, USER_HANDLE, PROJECT_HANDLE, ISSUE_HANDLE, - GLOBAL_COMMANDS_GROUP_TITLE, + PATH_HANDLE, PAGES_GROUP_TITLE, + PATH_GROUP_TITLE, GROUP_TITLES, } from './constants'; import SearchItem from './search_item.vue'; -import { commandMapper, linksReducer, autocompleteQuery } from './utils'; +import { commandMapper, linksReducer, autocompleteQuery, fileMapper } from './utils'; export default { name: 'CommandPaletteItems', @@ -25,7 +27,14 @@ export default { GlLoadingIcon, SearchItem, }, - inject: ['commandPaletteCommands', 'commandPaletteLinks', 'autocompletePath', 'searchContext'], + inject: [ + 'commandPaletteCommands', + 'commandPaletteLinks', + 'autocompletePath', + 'searchContext', + 'projectFilesPath', + 'projectBlobPath', + ], props: { searchQuery: { type: String, @@ -35,7 +44,7 @@ export default { type: String, required: true, validator: (value) => { - return COMMON_HANDLES.includes(value); + return [...COMMON_HANDLES, PATH_HANDLE].includes(value); }, }, }, @@ -43,13 +52,14 @@ export default { groups: [], error: null, loading: false, + projectFiles: [], }), computed: { isCommandMode() { return this.handle === COMMAND_HANDLE; }, - isUserMode() { - return this.handle === USER_HANDLE; + isPathMode() { + return this.handle === PATH_HANDLE; }, commands() { return this.commandPaletteCommands.map(commandMapper); @@ -62,7 +72,7 @@ export default { ? this.commands .map(({ name, items }) => { return { - name: name || GLOBAL_COMMANDS_GROUP_TITLE, + name, items: this.filterBySearchQuery(items, 'text'), }; }) @@ -73,7 +83,7 @@ export default { return this.groups?.length && this.groups.some((group) => group.items?.length); }, hasSearchQuery() { - if (this.isCommandMode) { + if (this.isCommandMode || this.isPathMode) { return this.searchQuery?.length > 0; } return this.searchQuery?.length > 2; @@ -84,6 +94,12 @@ export default { } return this.searchQuery; }, + filteredProjectFiles() { + if (!this.searchQuery) { + return this.projectFiles; + } + return this.filterBySearchQuery(this.projectFiles, 'text'); + }, }, watch: { searchQuery: { @@ -97,6 +113,9 @@ export default { case ISSUE_HANDLE: this.getScopedItems(); break; + case PATH_HANDLE: + this.getProjectFiles(); + break; default: break; } @@ -162,6 +181,26 @@ export default { }, ]; }, + async getProjectFiles() { + if (!this.projectFiles.length) { + this.loading = true; + try { + const response = await axios.get(this.projectFilesPath); + this.projectFiles = response?.data.map(fileMapper.bind(null, this.projectBlobPath)); + } catch (error) { + this.error = error; + } finally { + this.loading = false; + } + } + + this.groups = [ + { + name: PATH_GROUP_TITLE, + items: this.filteredProjectFiles, + }, + ]; + }, }, }; </script> diff --git a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/constants.js b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/constants.js index 9dab16984f50..780936c1b885 100644 --- a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/constants.js +++ b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/constants.js @@ -4,17 +4,19 @@ export const COMMAND_HANDLE = '>'; export const USER_HANDLE = '@'; export const PROJECT_HANDLE = '&'; export const ISSUE_HANDLE = '#'; +export const PATH_HANDLE = '/'; export const COMMON_HANDLES = [COMMAND_HANDLE, USER_HANDLE, PROJECT_HANDLE, ISSUE_HANDLE]; export const SEARCH_OR_COMMAND_MODE_PLACEHOLDER = sprintf( s__( - 'CommandPalette|Type %{commandHandle} for command, %{userHandle} for user, %{projectHandle} for project, %{issueHandle} for issue or perform generic search...', + 'CommandPalette|Type %{commandHandle} for command, %{userHandle} for user, %{projectHandle} for project, %{issueHandle} for issue, %{pathHandle} for project file or perform generic search...', ), { commandHandle: COMMAND_HANDLE, userHandle: USER_HANDLE, issueHandle: ISSUE_HANDLE, projectHandle: PROJECT_HANDLE, + pathHandle: PATH_HANDLE, }, false, ); @@ -24,6 +26,7 @@ export const SEARCH_SCOPE_PLACEHOLDER = { [USER_HANDLE]: s__('CommandPalette|user (enter at least 3 chars)'), [PROJECT_HANDLE]: s__('CommandPalette|project (enter at least 3 chars)'), [ISSUE_HANDLE]: s__('CommandPalette|issue (enter at least 3 chars)'), + [PATH_HANDLE]: s__('CommandPalette|go to project file'), }; export const SEARCH_SCOPE = { @@ -37,9 +40,11 @@ export const USERS_GROUP_TITLE = s__('GlobalSearch|Users'); export const PAGES_GROUP_TITLE = s__('CommandPalette|Pages'); export const PROJECTS_GROUP_TITLE = s__('GlobalSearch|Projects'); export const ISSUE_GROUP_TITLE = s__('GlobalSearch|Recent issues'); +export const PATH_GROUP_TITLE = s__('CommandPalette|Project files'); export const GROUP_TITLES = { [USER_HANDLE]: USERS_GROUP_TITLE, [PROJECT_HANDLE]: PROJECTS_GROUP_TITLE, [ISSUE_HANDLE]: ISSUE_GROUP_TITLE, + [PATH_HANDLE]: PATH_GROUP_TITLE, }; diff --git a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/fake_search_input.vue b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/fake_search_input.vue index dce2b24f551b..efd93e88fa9a 100644 --- a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/fake_search_input.vue +++ b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/fake_search_input.vue @@ -1,5 +1,5 @@ <script> -import { COMMON_HANDLES, SEARCH_SCOPE_PLACEHOLDER } from './constants'; +import { COMMON_HANDLES, PATH_HANDLE, SEARCH_SCOPE_PLACEHOLDER } from './constants'; export default { name: 'FakeSearchInput', @@ -11,7 +11,7 @@ export default { scope: { type: String, required: true, - validator: (value) => COMMON_HANDLES.includes(value), + validator: (value) => [...COMMON_HANDLES, PATH_HANDLE].includes(value), }, }, computed: { diff --git a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/utils.js b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/utils.js index 5c8c0e59eaf8..347a8ffb0b44 100644 --- a/app/assets/javascripts/super_sidebar/components/global_search/command_palette/utils.js +++ b/app/assets/javascripts/super_sidebar/components/global_search/command_palette/utils.js @@ -1,12 +1,12 @@ import { isNil, omitBy } from 'lodash'; -import { objectToQuery } from '~/lib/utils/url_utility'; -import { SEARCH_SCOPE } from './constants'; +import { objectToQuery, joinPaths } from '~/lib/utils/url_utility'; +import { SEARCH_SCOPE, GLOBAL_COMMANDS_GROUP_TITLE } from './constants'; export const commandMapper = ({ name, items }) => { // TODO: we filter out invite_members for now, because it is complicated to add the invite members modal here // and is out of scope for the basic command palette items. If it proves to be useful, we can add it later. return { - name, + name: name || GLOBAL_COMMANDS_GROUP_TITLE, items: items.filter(({ component }) => component !== 'invite_members'), }; }; @@ -32,6 +32,14 @@ export const linksReducer = (acc, menuItem) => { return acc; }; +export const fileMapper = (projectBlobPath, file) => { + return { + icon: 'doc-code', + text: file, + href: joinPaths(projectBlobPath, file), + }; +}; + export const autocompleteQuery = ({ path, searchTerm, handle, projectId }) => { const query = omitBy( { diff --git a/app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue b/app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue index cb34f2b8c269..30b10756ca92 100644 --- a/app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue +++ b/app/assets/javascripts/super_sidebar/components/global_search/components/global_search.vue @@ -38,7 +38,11 @@ import { } from '../constants'; import CommandPaletteItems from '../command_palette/command_palette_items.vue'; import FakeSearchInput from '../command_palette/fake_search_input.vue'; -import { COMMON_HANDLES, SEARCH_OR_COMMAND_MODE_PLACEHOLDER } from '../command_palette/constants'; +import { + COMMON_HANDLES, + PATH_HANDLE, + SEARCH_OR_COMMAND_MODE_PLACEHOLDER, +} from '../command_palette/constants'; import GlobalSearchAutocompleteItems from './global_search_autocomplete_items.vue'; import GlobalSearchDefaultItems from './global_search_default_items.vue'; import GlobalSearchScopedItems from './global_search_scoped_items.vue'; @@ -135,7 +139,11 @@ export default { return this.searchText?.trim().charAt(0); }, isCommandMode() { - return this.glFeatures?.commandPalette && COMMON_HANDLES.includes(this.searchTextFirstChar); + return ( + this.glFeatures?.commandPalette && + (COMMON_HANDLES.includes(this.searchTextFirstChar) || + (this.searchContext.project && this.searchTextFirstChar === PATH_HANDLE)) + ); }, commandPaletteQuery() { if (this.isCommandMode) { @@ -206,7 +214,7 @@ export default { } }, focusSearchInput() { - this.$refs.searchInputBox.$el.querySelector('input').focus(); + this.$refs.searchInput.$el.querySelector('input').focus(); }, focusNextItem(event, elements, offset) { const { target } = event; @@ -226,6 +234,13 @@ export default { } visitUrl(this.searchQuery); }, + onSearchModalShown() { + this.$emit('shown'); + }, + onSearchModalHidden() { + this.searchText = ''; + this.$emit('hidden'); + }, }, SEARCH_INPUT_DESCRIPTION, SEARCH_RESULTS_DESCRIPTION, @@ -243,8 +258,8 @@ export default { body-class="gl-p-0!" modal-class="global-search-modal" :centered="false" - @hidden="$emit('hidden')" - @shown="$emit('shown')" + @shown="onSearchModalShown" + @hide="onSearchModalHidden" > <form role="search" @@ -256,7 +271,7 @@ export default { <div class="gl-p-1 gl-relative"> <gl-search-box-by-type id="search" - ref="searchInputBox" + ref="searchInput" v-model="searchText" role="searchbox" data-testid="global-search-input" diff --git a/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js b/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js index f6afde02fa55..322eca720160 100644 --- a/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js +++ b/app/assets/javascripts/super_sidebar/super_sidebar_bundle.js @@ -65,13 +65,23 @@ export const initSuperSidebar = () => { if (!el) return false; - const { rootPath, sidebar, toggleNewNavEndpoint, forceDesktopExpandedSidebar } = el.dataset; + const { + rootPath, + sidebar, + toggleNewNavEndpoint, + forceDesktopExpandedSidebar, + commandPalette, + } = el.dataset; bindSuperSidebarCollapsedEvents(forceDesktopExpandedSidebar); initSuperSidebarCollapsedState(parseBoolean(forceDesktopExpandedSidebar)); const sidebarData = JSON.parse(sidebar); const searchData = convertObjectPropsToCamelCase(sidebarData.search); + + const commandPaletteData = JSON.parse(commandPalette); + const projectFilesPath = commandPaletteData.project_files_url; + const projectBlobPath = commandPaletteData.project_blob_url; const commandPaletteCommands = sidebarData.create_new_menu_groups || []; const commandPaletteLinks = convertObjectPropsToCamelCase(sidebarData.current_menu_items || []); @@ -91,6 +101,8 @@ export const initSuperSidebar = () => { commandPaletteLinks, autocompletePath, searchContext, + projectFilesPath, + projectBlobPath, }, store: createStore({ searchPath, diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb index 02a912d02277..bcb24aa0f7ec 100644 --- a/app/helpers/sidebars_helper.rb +++ b/app/helpers/sidebars_helper.rb @@ -123,6 +123,16 @@ def super_sidebar_nav_panel( end end + def command_palette_data(project: nil) + return {} unless project&.repo_exists? + return {} if project.empty_repo? + + { + project_files_url: project_files_path(project, project.default_branch, format: :json), + project_blob_url: project_blob_path(project, project.default_branch) + } + end + private def search_data diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 8e52f973e9e1..eb3b6587beff 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -7,7 +7,7 @@ - sidebar_panel = super_sidebar_nav_panel(nav: nav, user: current_user, group: group, project: @project, current_ref: current_ref, ref_type: @ref_type, viewed_user: @user) - sidebar_data = super_sidebar_context(current_user, group: group, project: @project, panel: sidebar_panel, panel_type: nav).to_json - %aside.js-super-sidebar.super-sidebar.super-sidebar-loading{ data: { root_path: root_path, sidebar: sidebar_data, toggle_new_nav_endpoint: profile_preferences_url, force_desktop_expanded_sidebar: @force_desktop_expanded_sidebar.to_s } } + %aside.js-super-sidebar.super-sidebar.super-sidebar-loading{ data: { root_path: root_path, sidebar: sidebar_data, toggle_new_nav_endpoint: profile_preferences_url, force_desktop_expanded_sidebar: @force_desktop_expanded_sidebar.to_s, command_palette: command_palette_data(project: @project).to_json } } - if display_whats_new? #whats-new-app{ data: { version_digest: whats_new_version_digest } } diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3cd30a587d8f..aa541d35083f 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -11241,12 +11241,18 @@ msgstr "" msgid "CommandPalette|Pages" msgstr "" -msgid "CommandPalette|Type %{commandHandle} for command, %{userHandle} for user, %{projectHandle} for project, %{issueHandle} for issue or perform generic search..." +msgid "CommandPalette|Project files" +msgstr "" + +msgid "CommandPalette|Type %{commandHandle} for command, %{userHandle} for user, %{projectHandle} for project, %{issueHandle} for issue, %{pathHandle} for project file or perform generic search..." msgstr "" msgid "CommandPalette|command" msgstr "" +msgid "CommandPalette|go to project file" +msgstr "" + msgid "CommandPalette|issue (enter at least 3 chars)" msgstr "" diff --git a/spec/frontend/super_sidebar/components/global_search/command_palette/command_palette_items_spec.js b/spec/frontend/super_sidebar/components/global_search/command_palette/command_palette_items_spec.js index 21d085dc0fb0..9714093e0016 100644 --- a/spec/frontend/super_sidebar/components/global_search/command_palette/command_palette_items_spec.js +++ b/spec/frontend/super_sidebar/components/global_search/command_palette/command_palette_items_spec.js @@ -6,18 +6,21 @@ import CommandPaletteItems from '~/super_sidebar/components/global_search/comman import { COMMAND_HANDLE, USERS_GROUP_TITLE, + PATH_GROUP_TITLE, USER_HANDLE, + PATH_HANDLE, SEARCH_SCOPE, } from '~/super_sidebar/components/global_search/command_palette/constants'; import { commandMapper, linksReducer, + fileMapper, } from '~/super_sidebar/components/global_search/command_palette/utils'; import { getFormattedItem } from '~/super_sidebar/components/global_search/utils'; import axios from '~/lib/utils/axios_utils'; import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import waitForPromises from 'helpers/wait_for_promises'; -import { COMMANDS, LINKS, USERS } from './mock_data'; +import { COMMANDS, LINKS, USERS, FILES } from './mock_data'; const links = LINKS.reduce(linksReducer, []); @@ -25,6 +28,8 @@ describe('CommandPaletteItems', () => { let wrapper; const autocompletePath = '/autocomplete'; const searchContext = { project: { id: 1 }, group: { id: 2 } }; + const projectFilesPath = 'project/files/path'; + const projectBlobPath = '/blob/main'; const createComponent = (props) => { wrapper = shallowMount(CommandPaletteItems, { @@ -42,6 +47,8 @@ describe('CommandPaletteItems', () => { commandPaletteLinks: LINKS, autocompletePath, searchContext, + projectFilesPath, + projectBlobPath, }, }); }; @@ -50,7 +57,7 @@ describe('CommandPaletteItems', () => { const findGroups = () => wrapper.findAllComponents(GlDisclosureDropdownGroup); const findLoader = () => wrapper.findComponent(GlLoadingIcon); - describe('COMMANDS & LINKS', () => { + describe('Commands and links', () => { it('renders all commands initially', () => { createComponent(); const commandGroup = COMMANDS.map(commandMapper)[0]; @@ -90,7 +97,7 @@ describe('CommandPaletteItems', () => { }); }); - describe('USERS, ISSUES, PROJECTS', () => { + describe('Users, issues, and projects', () => { let mockAxios; beforeEach(() => { @@ -140,4 +147,64 @@ describe('CommandPaletteItems', () => { expect(wrapper.text()).toBe('No results found'); }); }); + + describe('Project files', () => { + let mockAxios; + + beforeEach(() => { + mockAxios = new MockAdapter(axios); + }); + + it('should request project files on first search', () => { + jest.spyOn(axios, 'get'); + const searchQuery = 'gitlab-ci.yml'; + createComponent({ handle: PATH_HANDLE, searchQuery }); + + expect(axios.get).toHaveBeenCalledWith(projectFilesPath); + expect(findLoader().exists()).toBe(true); + }); + + it('should render returned items', async () => { + const items = FILES.map(fileMapper.bind(null, projectBlobPath)); + mockAxios.onGet().replyOnce(HTTP_STATUS_OK, FILES); + jest.spyOn(fuzzaldrinPlus, 'filter').mockReturnValue(items); + + const searchQuery = 'gitlab-ci.yml'; + createComponent({ handle: PATH_HANDLE, searchQuery }); + + await waitForPromises(); + + expect(findItems()).toHaveLength(items.length); + expect(findGroups().at(0).props('group')).toMatchObject({ + name: PATH_GROUP_TITLE, + items, + }); + }); + + it('should display no results message when no files matched the search query', async () => { + mockAxios.onGet().replyOnce(HTTP_STATUS_OK, []); + const searchQuery = 'gitlab-ci.yml'; + createComponent({ handle: PATH_HANDLE, searchQuery }); + await waitForPromises(); + expect(wrapper.text()).toBe('No results found'); + }); + + it('should not make additional server call on the search query change', async () => { + const searchQuery = 'gitlab-ci.yml'; + const newSearchQuery = 'package.json'; + + jest.spyOn(axios, 'get'); + + createComponent({ handle: PATH_HANDLE, searchQuery }); + + mockAxios.onGet().replyOnce(HTTP_STATUS_OK, FILES); + await waitForPromises(); + + expect(axios.get).toHaveBeenCalledTimes(1); + + await wrapper.setProps({ searchQuery: newSearchQuery }); + + expect(axios.get).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/spec/frontend/super_sidebar/components/global_search/command_palette/mock_data.js b/spec/frontend/super_sidebar/components/global_search/command_palette/mock_data.js index ec65a43d549b..26a6501c3389 100644 --- a/spec/frontend/super_sidebar/components/global_search/command_palette/mock_data.js +++ b/spec/frontend/super_sidebar/components/global_search/command_palette/mock_data.js @@ -131,3 +131,15 @@ export const ISSUE = { project_name: 'Flight', url: '/flightjs/Flight/-/issues/37', }; + +export const FILES = [ + '.codeclimate.yml', + '.gitignore', + '.gitlab-ci.yml', + '.gitlab/CODEOWNERS', + '.ruby-version', + '.tool-versions', + 'CHANGELOG', + 'CONTRIBUTING.md', + 'Dangerfile', +]; diff --git a/spec/frontend/super_sidebar/components/global_search/command_palette/utils_spec.js b/spec/frontend/super_sidebar/components/global_search/command_palette/utils_spec.js index 0b75787723e5..ebc52e2d910a 100644 --- a/spec/frontend/super_sidebar/components/global_search/command_palette/utils_spec.js +++ b/spec/frontend/super_sidebar/components/global_search/command_palette/utils_spec.js @@ -1,6 +1,7 @@ import { commandMapper, linksReducer, + fileMapper, } from '~/super_sidebar/components/global_search/command_palette/utils'; import { COMMANDS, LINKS, TRANSFORMED_LINKS } from './mock_data'; @@ -16,3 +17,15 @@ describe('commandMapper', () => { expect(COMMANDS.map(commandMapper)[0].items).toHaveLength(initialCommandsLength - 1); }); }); + +describe('fileMapper', () => { + it('should transform files', () => { + const file = 'file'; + const projectBlobPath = 'project/blob/path'; + expect(fileMapper(projectBlobPath, file)).toEqual({ + icon: 'doc-code', + text: file, + href: `${projectBlobPath}/${file}`, + }); + }); +}); diff --git a/spec/frontend/super_sidebar/components/global_search/components/global_search_spec.js b/spec/frontend/super_sidebar/components/global_search/components/global_search_spec.js index 9b7b9e288dfb..cfbe508f3d08 100644 --- a/spec/frontend/super_sidebar/components/global_search/components/global_search_spec.js +++ b/spec/frontend/super_sidebar/components/global_search/components/global_search_spec.js @@ -12,6 +12,7 @@ import CommandPaletteItems from '~/super_sidebar/components/global_search/comman import { SEARCH_OR_COMMAND_MODE_PLACEHOLDER, COMMON_HANDLES, + PATH_HANDLE, } from '~/super_sidebar/components/global_search/command_palette/constants'; import { SEARCH_INPUT_DESCRIPTION, @@ -319,7 +320,7 @@ describe('GlobalSearchModal', () => { }); }); - describe.each(COMMON_HANDLES)( + describe.each([...COMMON_HANDLES, PATH_HANDLE])( 'when FF `command_palette` is enabled and search handle is %s', (handle) => { beforeEach(() => { @@ -415,7 +416,7 @@ describe('GlobalSearchModal', () => { describe('Modal events', () => { beforeEach(() => { - createComponent(); + createComponent({ search: 'searchQuery' }); }); it('should emit `shown` event when modal shown`', () => { @@ -423,9 +424,10 @@ describe('GlobalSearchModal', () => { expect(wrapper.emitted('shown')).toHaveLength(1); }); - it('should emit `hidden` event when modal hidden`', () => { - findGlobalSearchModal().vm.$emit('hidden'); + it('should emit `hidden` event when modal hidden and clear the search input', () => { + findGlobalSearchModal().vm.$emit('hide'); expect(wrapper.emitted('hidden')).toHaveLength(1); + expect(actionSpies.setSearch).toHaveBeenCalledWith(expect.any(Object), ''); }); }); }); diff --git a/spec/helpers/sidebars_helper_spec.rb b/spec/helpers/sidebars_helper_spec.rb index 6648663b6344..8f0bf156d1d9 100644 --- a/spec/helpers/sidebars_helper_spec.rb +++ b/spec/helpers/sidebars_helper_spec.rb @@ -528,4 +528,31 @@ expect(helper.super_sidebar_nav_panel(user: user)).to be_a(Sidebars::YourWork::Panel) end end + + describe '#command_palette_data' do + it 'returns data for project files search' do + project = create(:project, :repository) # rubocop:disable RSpec/FactoryBot/AvoidCreate + + expect(helper.command_palette_data(project: project)).to eq( + project_files_url: project_files_path( + project, project.default_branch, format: :json), + project_blob_url: project_blob_path( + project, project.default_branch) + ) + end + + it 'returns empty object when project is nil' do + expect(helper.command_palette_data(project: nil)).to eq({}) + end + + it 'returns empty object when project does not have repo' do + project = build(:project) + expect(helper.command_palette_data(project: project)).to eq({}) + end + + it 'returns empty object when project has repo but it is empty' do + project = build(:project, :empty_repo) + expect(helper.command_palette_data(project: project)).to eq({}) + end + end end -- GitLab