mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
Add install as an option when extension is selected. (#20358)
This commit is contained in:
@@ -21,6 +21,10 @@ import {
|
|||||||
ConfigExtensionDialog,
|
ConfigExtensionDialog,
|
||||||
type ConfigExtensionDialogProps,
|
type ConfigExtensionDialogProps,
|
||||||
} from '../components/ConfigExtensionDialog.js';
|
} from '../components/ConfigExtensionDialog.js';
|
||||||
|
import {
|
||||||
|
ExtensionRegistryView,
|
||||||
|
type ExtensionRegistryViewProps,
|
||||||
|
} from '../components/views/ExtensionRegistryView.js';
|
||||||
import { type CommandContext, type SlashCommand } from './types.js';
|
import { type CommandContext, type SlashCommand } from './types.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -39,6 +43,8 @@ import {
|
|||||||
} from '../../config/extension-manager.js';
|
} from '../../config/extension-manager.js';
|
||||||
import { SettingScope } from '../../config/settings.js';
|
import { SettingScope } from '../../config/settings.js';
|
||||||
import { stat } from 'node:fs/promises';
|
import { stat } from 'node:fs/promises';
|
||||||
|
import { type RegistryExtension } from '../../config/extensionRegistryClient.js';
|
||||||
|
import { waitFor } from '../../test-utils/async.js';
|
||||||
|
|
||||||
vi.mock('../../config/extension-manager.js', async (importOriginal) => {
|
vi.mock('../../config/extension-manager.js', async (importOriginal) => {
|
||||||
const actual =
|
const actual =
|
||||||
@@ -167,6 +173,7 @@ describe('extensionsCommand', () => {
|
|||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
dispatchExtensionStateUpdate: mockDispatchExtensionState,
|
dispatchExtensionStateUpdate: mockDispatchExtensionState,
|
||||||
|
removeComponent: vi.fn(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -429,6 +436,61 @@ describe('extensionsCommand', () => {
|
|||||||
throw new Error('Explore action not found');
|
throw new Error('Explore action not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it('should return ExtensionRegistryView custom dialog when experimental.extensionRegistry is true', async () => {
|
||||||
|
mockContext.services.settings.merged.experimental.extensionRegistry = true;
|
||||||
|
|
||||||
|
const result = await exploreAction(mockContext, '');
|
||||||
|
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
if (result?.type !== 'custom_dialog') {
|
||||||
|
throw new Error('Expected custom_dialog');
|
||||||
|
}
|
||||||
|
|
||||||
|
const component =
|
||||||
|
result.component as ReactElement<ExtensionRegistryViewProps>;
|
||||||
|
expect(component.type).toBe(ExtensionRegistryView);
|
||||||
|
expect(component.props.extensionManager).toBe(mockExtensionLoader);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle onSelect and onClose in ExtensionRegistryView', async () => {
|
||||||
|
mockContext.services.settings.merged.experimental.extensionRegistry = true;
|
||||||
|
|
||||||
|
const result = await exploreAction(mockContext, '');
|
||||||
|
if (result?.type !== 'custom_dialog') {
|
||||||
|
throw new Error('Expected custom_dialog');
|
||||||
|
}
|
||||||
|
|
||||||
|
const component =
|
||||||
|
result.component as ReactElement<ExtensionRegistryViewProps>;
|
||||||
|
|
||||||
|
const extension = {
|
||||||
|
extensionName: 'test-ext',
|
||||||
|
url: 'https://github.com/test/ext.git',
|
||||||
|
} as RegistryExtension;
|
||||||
|
|
||||||
|
vi.mocked(inferInstallMetadata).mockResolvedValue({
|
||||||
|
source: extension.url,
|
||||||
|
type: 'git',
|
||||||
|
});
|
||||||
|
mockInstallExtension.mockResolvedValue({ name: extension.url });
|
||||||
|
|
||||||
|
// Call onSelect
|
||||||
|
component.props.onSelect?.(extension);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(inferInstallMetadata).toHaveBeenCalledWith(extension.url);
|
||||||
|
expect(mockInstallExtension).toHaveBeenCalledWith({
|
||||||
|
source: extension.url,
|
||||||
|
type: 'git',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
expect(mockContext.ui.removeComponent).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// Call onClose
|
||||||
|
component.props.onClose?.();
|
||||||
|
expect(mockContext.ui.removeComponent).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
it("should add an info message and call 'open' in a non-sandbox environment", async () => {
|
it("should add an info message and call 'open' in a non-sandbox environment", async () => {
|
||||||
// Ensure no special environment variables that would affect behavior
|
// Ensure no special environment variables that would affect behavior
|
||||||
vi.stubEnv('NODE_ENV', '');
|
vi.stubEnv('NODE_ENV', '');
|
||||||
|
|||||||
@@ -280,7 +280,9 @@ async function exploreAction(
|
|||||||
type: 'custom_dialog' as const,
|
type: 'custom_dialog' as const,
|
||||||
component: React.createElement(ExtensionRegistryView, {
|
component: React.createElement(ExtensionRegistryView, {
|
||||||
onSelect: (extension) => {
|
onSelect: (extension) => {
|
||||||
debugLogger.debug(`Selected extension: ${extension.extensionName}`);
|
debugLogger.log(`Selected extension: ${extension.extensionName}`);
|
||||||
|
void installAction(context, extension.url);
|
||||||
|
context.ui.removeComponent();
|
||||||
},
|
},
|
||||||
onClose: () => context.ui.removeComponent(),
|
onClose: () => context.ui.removeComponent(),
|
||||||
extensionManager,
|
extensionManager,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { useRegistrySearch } from '../../hooks/useRegistrySearch.js';
|
|||||||
|
|
||||||
import { useUIState } from '../../contexts/UIStateContext.js';
|
import { useUIState } from '../../contexts/UIStateContext.js';
|
||||||
|
|
||||||
interface ExtensionRegistryViewProps {
|
export interface ExtensionRegistryViewProps {
|
||||||
onSelect?: (extension: RegistryExtension) => void;
|
onSelect?: (extension: RegistryExtension) => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
extensionManager: ExtensionManager;
|
extensionManager: ExtensionManager;
|
||||||
|
|||||||
Reference in New Issue
Block a user