Download packages/cli/src/ui/hooks/useExtensionUpdates.test.tsx from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 9.78 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/ui/hooks/useExtensionUpdates.test.tsx
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/cli/src/ui/hooks/useExtensionUpdates.test.tsx
-
curl -L -o useExtensionUpdates.test.tsx https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/cli/src/ui/hooks/useExtensionUpdates.test.tsx
9.78 kB
| /** | |
| * @license | |
| * Copyright 2025 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'; | |
| import * as fs from 'node:fs'; | |
| import * as os from 'node:os'; | |
| import * as path from 'node:path'; | |
| import { createExtension } from '../../test-utils/createExtension.js'; | |
| import { useExtensionUpdates } from './useExtensionUpdates.js'; | |
| import { | |
| GEMINI_DIR, | |
| loadAgentsFromDirectory, | |
| loadSkillsFromDir, | |
| } from '@google/gemini-cli-core'; | |
| import { render } from '../../test-utils/render.js'; | |
| import { waitFor } from '../../test-utils/async.js'; | |
| import { MessageType } from '../types.js'; | |
| import { | |
| checkForAllExtensionUpdates, | |
| updateExtension, | |
| } from '../../config/extensions/update.js'; | |
| import { ExtensionUpdateState } from '../state/extensions.js'; | |
| import { ExtensionManager } from '../../config/extension-manager.js'; | |
| import { | |
| loadSettings, | |
| resetSettingsCacheForTesting, | |
| } from '../../config/settings.js'; | |
| vi.mock('os', async (importOriginal) => { | |
| const mockedOs = await importOriginal<typeof os>(); | |
| return { | |
| ...mockedOs, | |
| homedir: vi.fn().mockReturnValue('/tmp/mock-home'), | |
| }; | |
| }); | |
| vi.mock('@google/gemini-cli-core', async (importOriginal) => { | |
| const actual = | |
| await importOriginal<typeof import('@google/gemini-cli-core')>(); | |
| return { | |
| ...actual, | |
| homedir: () => os.homedir(), | |
| loadAgentsFromDirectory: vi | |
| .fn() | |
| .mockResolvedValue({ agents: [], errors: [] }), | |
| loadSkillsFromDir: vi.fn().mockResolvedValue([]), | |
| }; | |
| }); | |
| vi.mock('../../config/extensions/update.js', () => ({ | |
| checkForAllExtensionUpdates: vi.fn(), | |
| updateExtension: vi.fn(), | |
| })); | |
| describe('useExtensionUpdates', () => { | |
| let tempHomeDir: string; | |
| let tempWorkspaceDir: string; | |
| let userExtensionsDir: string; | |
| let extensionManager: ExtensionManager; | |
| beforeEach(() => { | |
| resetSettingsCacheForTesting(); | |
| vi.mocked(loadAgentsFromDirectory).mockResolvedValue({ | |
| agents: [], | |
| errors: [], | |
| }); | |
| vi.mocked(loadSkillsFromDir).mockResolvedValue([]); | |
| tempHomeDir = fs.mkdtempSync( | |
| path.join(os.tmpdir(), 'gemini-cli-test-home-'), | |
| ); | |
| vi.mocked(os.homedir).mockReturnValue(tempHomeDir); | |
| tempWorkspaceDir = fs.mkdtempSync( | |
| path.join(tempHomeDir, 'gemini-cli-test-workspace-'), | |
| ); | |
| vi.spyOn(process, 'cwd').mockReturnValue(tempWorkspaceDir); | |
| userExtensionsDir = path.join(tempHomeDir, GEMINI_DIR, 'extensions'); | |
| fs.mkdirSync(userExtensionsDir, { recursive: true }); | |
| vi.mocked(checkForAllExtensionUpdates).mockReset(); | |
| vi.mocked(updateExtension).mockReset(); | |
| extensionManager = new ExtensionManager({ | |
| workspaceDir: tempHomeDir, | |
| requestConsent: vi.fn(), | |
| requestSetting: vi.fn(), | |
| settings: loadSettings().merged, | |
| }); | |
| }); | |
| afterEach(() => { | |
| fs.rmSync(tempHomeDir, { recursive: true, force: true }); | |
| }); | |
| it('should check for updates and log a message if an update is available', async () => { | |
| vi.spyOn(extensionManager, 'getExtensions').mockReturnValue([ | |
| { | |
| name: 'test-extension', | |
| id: 'test-extension-id', | |
| version: '1.0.0', | |
| path: '/some/path', | |
| isActive: true, | |
| installMetadata: { | |
| type: 'git', | |
| source: 'https://some/repo', | |
| autoUpdate: false, | |
| }, | |
| contextFiles: [], | |
| }, | |
| ]); | |
| const addItem = vi.fn(); | |
| vi.mocked(checkForAllExtensionUpdates).mockImplementation( | |
| async (_extensions, _extensionManager, dispatch) => { | |
| dispatch({ | |
| type: 'SET_STATE', | |
| payload: { | |
| name: 'test-extension', | |
| state: ExtensionUpdateState.UPDATE_AVAILABLE, | |
| }, | |
| }); | |
| }, | |
| ); | |
| function TestComponent() { | |
| useExtensionUpdates(extensionManager, addItem, false); | |
| return null; | |
| } | |
| await render(<TestComponent />); | |
| await waitFor(() => { | |
| expect(addItem).toHaveBeenCalledWith( | |
| { | |
| type: MessageType.INFO, | |
| text: `You have 1 extension with an update available. Run "/extensions update test-extension".`, | |
| }, | |
| expect.any(Number), | |
| ); | |
| }); | |
| }); | |
| it('should check for updates and automatically update if autoUpdate is true', async () => { | |
| createExtension({ | |
| extensionsDir: userExtensionsDir, | |
| name: 'test-extension', | |
| version: '1.0.0', | |
| installMetadata: { | |
| source: 'https://some.git/repo', | |
| type: 'git', | |
| autoUpdate: true, | |
| }, | |
| }); | |
| await extensionManager.loadExtensions(); | |
| const addItem = vi.fn(); | |
| vi.mocked(checkForAllExtensionUpdates).mockImplementation( | |
| async (_extensions, _extensionManager, dispatch) => { | |
| dispatch({ | |
| type: 'SET_STATE', | |
| payload: { | |
| name: 'test-extension', | |
| state: ExtensionUpdateState.UPDATE_AVAILABLE, | |
| }, | |
| }); | |
| }, | |
| ); | |
| vi.mocked(updateExtension).mockResolvedValue({ | |
| originalVersion: '1.0.0', | |
| updatedVersion: '1.1.0', | |
| name: '', | |
| }); | |
| function TestComponent() { | |
| useExtensionUpdates(extensionManager, addItem, false); | |
| return null; | |
| } | |
| await render(<TestComponent />); | |
| await waitFor( | |
| () => { | |
| expect(addItem).toHaveBeenCalledWith( | |
| { | |
| type: MessageType.INFO, | |
| text: 'Extension "test-extension" successfully updated: 1.0.0 → 1.1.0.', | |
| }, | |
| expect.any(Number), | |
| ); | |
| }, | |
| { timeout: 4000 }, | |
| ); | |
| }); | |
| it('should batch update notifications for multiple extensions', async () => { | |
| createExtension({ | |
| extensionsDir: userExtensionsDir, | |
| name: 'test-extension-1', | |
| version: '1.0.0', | |
| installMetadata: { | |
| source: 'https://some.git/repo1', | |
| type: 'git', | |
| autoUpdate: true, | |
| }, | |
| }); | |
| createExtension({ | |
| extensionsDir: userExtensionsDir, | |
| name: 'test-extension-2', | |
| version: '2.0.0', | |
| installMetadata: { | |
| source: 'https://some.git/repo2', | |
| type: 'git', | |
| autoUpdate: true, | |
| }, | |
| }); | |
| await extensionManager.loadExtensions(); | |
| const addItem = vi.fn(); | |
| vi.mocked(checkForAllExtensionUpdates).mockImplementation( | |
| async (_extensions, _extensionManager, dispatch) => { | |
| dispatch({ | |
| type: 'SET_STATE', | |
| payload: { | |
| name: 'test-extension-1', | |
| state: ExtensionUpdateState.UPDATE_AVAILABLE, | |
| }, | |
| }); | |
| dispatch({ | |
| type: 'SET_STATE', | |
| payload: { | |
| name: 'test-extension-2', | |
| state: ExtensionUpdateState.UPDATE_AVAILABLE, | |
| }, | |
| }); | |
| }, | |
| ); | |
| vi.mocked(updateExtension) | |
| .mockResolvedValueOnce({ | |
| originalVersion: '1.0.0', | |
| updatedVersion: '1.1.0', | |
| name: '', | |
| }) | |
| .mockResolvedValueOnce({ | |
| originalVersion: '2.0.0', | |
| updatedVersion: '2.1.0', | |
| name: '', | |
| }); | |
| function TestComponent() { | |
| useExtensionUpdates(extensionManager, addItem, false); | |
| return null; | |
| } | |
| await render(<TestComponent />); | |
| await waitFor( | |
| () => { | |
| expect(addItem).toHaveBeenCalledTimes(2); | |
| expect(addItem).toHaveBeenCalledWith( | |
| { | |
| type: MessageType.INFO, | |
| text: 'Extension "test-extension-1" successfully updated: 1.0.0 → 1.1.0.', | |
| }, | |
| expect.any(Number), | |
| ); | |
| expect(addItem).toHaveBeenCalledWith( | |
| { | |
| type: MessageType.INFO, | |
| text: 'Extension "test-extension-2" successfully updated: 2.0.0 → 2.1.0.', | |
| }, | |
| expect.any(Number), | |
| ); | |
| }, | |
| { timeout: 4000 }, | |
| ); | |
| }); | |
| it('should batch update notifications for multiple extensions with autoUpdate: false', async () => { | |
| vi.spyOn(extensionManager, 'getExtensions').mockReturnValue([ | |
| { | |
| name: 'test-extension-1', | |
| id: 'test-extension-1-id', | |
| version: '1.0.0', | |
| path: '/some/path1', | |
| isActive: true, | |
| installMetadata: { | |
| type: 'git', | |
| source: 'https://some/repo1', | |
| autoUpdate: false, | |
| }, | |
| contextFiles: [], | |
| }, | |
| { | |
| name: 'test-extension-2', | |
| id: 'test-extension-2-id', | |
| version: '2.0.0', | |
| path: '/some/path2', | |
| isActive: true, | |
| installMetadata: { | |
| type: 'git', | |
| source: 'https://some/repo2', | |
| autoUpdate: false, | |
| }, | |
| contextFiles: [], | |
| }, | |
| ]); | |
| const addItem = vi.fn(); | |
| vi.mocked(checkForAllExtensionUpdates).mockImplementation( | |
| async (_extensions, _extensionManager, dispatch) => { | |
| dispatch({ type: 'BATCH_CHECK_START' }); | |
| dispatch({ | |
| type: 'SET_STATE', | |
| payload: { | |
| name: 'test-extension-1', | |
| state: ExtensionUpdateState.UPDATE_AVAILABLE, | |
| }, | |
| }); | |
| await new Promise((r) => setTimeout(r, 50)); | |
| dispatch({ | |
| type: 'SET_STATE', | |
| payload: { | |
| name: 'test-extension-2', | |
| state: ExtensionUpdateState.UPDATE_AVAILABLE, | |
| }, | |
| }); | |
| dispatch({ type: 'BATCH_CHECK_END' }); | |
| }, | |
| ); | |
| function TestComponent() { | |
| useExtensionUpdates(extensionManager, addItem, false); | |
| return null; | |
| } | |
| await render(<TestComponent />); | |
| await waitFor(() => { | |
| expect(addItem).toHaveBeenCalledTimes(1); | |
| expect(addItem).toHaveBeenCalledWith( | |
| { | |
| type: MessageType.INFO, | |
| text: `You have 2 extensions with an update available. Run "/extensions update test-extension-1 test-extension-2".`, | |
| }, | |
| expect.any(Number), | |
| ); | |
| }); | |
| }); | |
| }); | |