-
Notifications
You must be signed in to change notification settings - Fork 9.5k
feat: Add explore subcommand for extension #11846
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
52d553f
c9f72e0
baebbc1
8960398
695f186
9ef9dad
7e7eb1d
1014ee0
5b3a21c
53c26af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,9 +9,15 @@ import { createMockCommandContext } from '../../test-utils/mockCommandContext.js | |
| import { MessageType } from '../types.js'; | ||
| import { extensionsCommand } from './extensionsCommand.js'; | ||
| import { type CommandContext } from './types.js'; | ||
| import { describe, it, expect, vi, beforeEach } from 'vitest'; | ||
| import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; | ||
| import { type ExtensionUpdateAction } from '../state/extensions.js'; | ||
|
|
||
| // Mock the 'open' library similar to docsCommand.test.ts | ||
| import open from 'open'; | ||
| vi.mock('open', () => ({ | ||
| default: vi.fn(), | ||
| })); | ||
|
|
||
| vi.mock('../../config/extensions/update.js', () => ({ | ||
| updateExtension: vi.fn(), | ||
| checkForAllExtensionUpdates: vi.fn(), | ||
|
|
@@ -26,6 +32,8 @@ describe('extensionsCommand', () => { | |
| beforeEach(() => { | ||
| vi.resetAllMocks(); | ||
| mockGetExtensions.mockReturnValue([]); | ||
| // Reset the `open` mock as done in docsCommand.test.ts | ||
|
||
| vi.mocked(open).mockClear(); | ||
| mockContext = createMockCommandContext({ | ||
| services: { | ||
| config: { | ||
|
|
@@ -39,6 +47,11 @@ describe('extensionsCommand', () => { | |
| }); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| // Restore any stubbed environment variables, similar to docsCommand.test.ts | ||
| vi.unstubAllEnvs(); | ||
| }); | ||
|
|
||
| describe('list', () => { | ||
| it('should add an EXTENSIONS_LIST item to the UI', async () => { | ||
| if (!extensionsCommand.action) throw new Error('Action not defined'); | ||
|
|
@@ -302,4 +315,72 @@ describe('extensionsCommand', () => { | |
| }); | ||
| }); | ||
| }); | ||
|
|
||
| describe('explore', () => { | ||
| const exploreAction = extensionsCommand.subCommands?.find( | ||
| (cmd) => cmd.name === 'explore', | ||
| )?.action; | ||
|
|
||
| if (!exploreAction) { | ||
| throw new Error('Explore action not found'); | ||
| } | ||
|
|
||
| it("should add an info message and call 'open' in a non-sandbox environment", async () => { | ||
| // Ensure no special environment variables that would affect behavior | ||
| vi.stubEnv('NODE_ENV', ''); | ||
| vi.stubEnv('SANDBOX', ''); | ||
|
|
||
| await exploreAction(mockContext, ''); | ||
|
|
||
| const extensionsUrl = 'https://geminicli.com/extensions/'; | ||
| expect(mockContext.ui.addItem).toHaveBeenCalledWith( | ||
| { | ||
| type: MessageType.INFO, | ||
| text: `Opening extensions page in your browser: ${extensionsUrl}`, | ||
| }, | ||
| expect.any(Number), | ||
| ); | ||
|
|
||
| expect(open).toHaveBeenCalledWith(extensionsUrl); | ||
| }); | ||
|
|
||
| it('should only add an info message in a sandbox environment', async () => { | ||
| // Simulate a sandbox environment | ||
| vi.stubEnv('NODE_ENV', ''); | ||
| vi.stubEnv('SANDBOX', 'gemini-sandbox'); | ||
| const extensionsUrl = 'https://geminicli.com/extensions/'; | ||
|
|
||
| await exploreAction(mockContext, ''); | ||
|
|
||
| expect(mockContext.ui.addItem).toHaveBeenCalledWith( | ||
| { | ||
| type: MessageType.INFO, | ||
| text: `Please open the following URL in your browser to explore extensions:\\n${extensionsUrl}`, | ||
| }, | ||
| expect.any(Number), | ||
| ); | ||
|
|
||
| // Ensure 'open' was not called in the sandbox | ||
| expect(open).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should add an info message and not call open in NODE_ENV test environment', async () => { | ||
| vi.stubEnv('NODE_ENV', 'test'); | ||
| vi.stubEnv('SANDBOX', ''); | ||
| const extensionsUrl = 'https://geminicli.com/extensions/'; | ||
|
|
||
| await exploreAction(mockContext, ''); | ||
|
|
||
| expect(mockContext.ui.addItem).toHaveBeenCalledWith( | ||
| { | ||
| type: MessageType.INFO, | ||
| text: `Would open extensions page in your browser: ${extensionsUrl} (skipped in test environment)`, | ||
| }, | ||
| expect.any(Number), | ||
| ); | ||
|
|
||
| // Ensure 'open' was not called in test environment | ||
| expect(open).not.toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While the existing tests cover the success paths well, a key scenario is missing. It's important to also test the failure case where |
||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,8 @@ import { | |
| type SlashCommand, | ||
| CommandKind, | ||
| } from './types.js'; | ||
| import open from 'open'; | ||
| import process from 'node:process'; | ||
|
|
||
| async function listAction(context: CommandContext) { | ||
| const historyItem: HistoryItemExtensionsList = { | ||
|
|
@@ -112,6 +114,41 @@ function updateAction(context: CommandContext, args: string): Promise<void> { | |
| return updateComplete.then((_) => {}); | ||
| } | ||
|
|
||
| async function exploreAction(context: CommandContext) { | ||
| const extensionsUrl = 'https://geminicli.com/extensions/'; | ||
|
|
||
| // Only check for NODE_ENV for explicit test mode, not for unit test framework | ||
| if (process.env['NODE_ENV'] === 'test') { | ||
| context.ui.addItem( | ||
| { | ||
| type: MessageType.INFO, | ||
| text: `Would open extensions page in your browser: ${extensionsUrl} (skipped in test environment)`, | ||
| }, | ||
| Date.now(), | ||
| ); | ||
| } else if ( | ||
| process.env['SANDBOX'] && | ||
| process.env['SANDBOX'] !== 'sandbox-exec' | ||
| ) { | ||
| context.ui.addItem( | ||
| { | ||
| type: MessageType.INFO, | ||
| text: `Please open the following URL in your browser to explore extensions:\\n${extensionsUrl}`, | ||
|
||
| }, | ||
| Date.now(), | ||
| ); | ||
| } else { | ||
| context.ui.addItem( | ||
| { | ||
| type: MessageType.INFO, | ||
| text: `Opening extensions page in your browser: ${extensionsUrl}`, | ||
| }, | ||
| Date.now(), | ||
| ); | ||
| await open(extensionsUrl); | ||
|
||
| } | ||
| } | ||
|
|
||
| const listExtensionsCommand: SlashCommand = { | ||
| name: 'list', | ||
| description: 'List active extensions', | ||
|
|
@@ -141,11 +178,22 @@ const updateExtensionsCommand: SlashCommand = { | |
| }, | ||
| }; | ||
|
|
||
| const exploreExtensionsCommand: SlashCommand = { | ||
| name: 'explore', | ||
| description: 'Open extensions page in your browser', | ||
| kind: CommandKind.BUILT_IN, | ||
| action: exploreAction, | ||
| }; | ||
|
|
||
| export const extensionsCommand: SlashCommand = { | ||
| name: 'extensions', | ||
| description: 'Manage extensions', | ||
| kind: CommandKind.BUILT_IN, | ||
| subCommands: [listExtensionsCommand, updateExtensionsCommand], | ||
| subCommands: [ | ||
| listExtensionsCommand, | ||
| updateExtensionsCommand, | ||
| exploreExtensionsCommand, | ||
| ], | ||
| action: (context, args) => | ||
| // Default to list if no subcommand is provided | ||
| listExtensionsCommand.action!(context, args), | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: remove this comment