Download packages/core/src/ide/ideContext.test.ts from SaylorTwift/gemini-cli: direct link, hf CLI and curl.
- Browser
- Download file 12.7 kB
-
https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/core/src/ide/ideContext.test.ts
- Command line
-
hf download hf://SaylorTwift/gemini-cli/packages/core/src/ide/ideContext.test.ts
-
curl -L -o ideContext.test.ts https://huggingface.co/SaylorTwift/gemini-cli/resolve/main/packages/core/src/ide/ideContext.test.ts
12.7 kB
| /** | |
| * @license | |
| * Copyright 2025 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import { | |
| IDE_MAX_OPEN_FILES, | |
| IDE_MAX_SELECTED_TEXT_LENGTH, | |
| } from './constants.js'; | |
| import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; | |
| import { IdeContextStore } from './ideContext.js'; | |
| import { | |
| type IdeContext, | |
| FileSchema, | |
| IdeContextSchema, | |
| type File, | |
| } from './types.js'; | |
| describe('ideContext', () => { | |
| describe('createIdeContextStore', () => { | |
| let ideContextStore: IdeContextStore; | |
| beforeEach(() => { | |
| // Create a fresh, isolated instance for each test | |
| ideContextStore = new IdeContextStore(); | |
| }); | |
| afterEach(() => { | |
| vi.restoreAllMocks(); | |
| }); | |
| it('should return undefined initially for ide context', () => { | |
| expect(ideContextStore.get()).toBeUndefined(); | |
| }); | |
| it('should set and retrieve the ide context', () => { | |
| const testFile = { | |
| workspaceState: { | |
| openFiles: [ | |
| { | |
| path: '/path/to/test/file.ts', | |
| isActive: true, | |
| selectedText: '1234', | |
| timestamp: 0, | |
| }, | |
| ], | |
| }, | |
| }; | |
| ideContextStore.set(testFile); | |
| const activeFile = ideContextStore.get(); | |
| expect(activeFile).toEqual(testFile); | |
| }); | |
| it('should update the ide context when called multiple times', () => { | |
| const firstFile = { | |
| workspaceState: { | |
| openFiles: [ | |
| { | |
| path: '/path/to/first.js', | |
| isActive: true, | |
| selectedText: '1234', | |
| timestamp: 0, | |
| }, | |
| ], | |
| }, | |
| }; | |
| ideContextStore.set(firstFile); | |
| const secondFile = { | |
| workspaceState: { | |
| openFiles: [ | |
| { | |
| path: '/path/to/second.py', | |
| isActive: true, | |
| cursor: { line: 20, character: 30 }, | |
| timestamp: 0, | |
| }, | |
| ], | |
| }, | |
| }; | |
| ideContextStore.set(secondFile); | |
| const activeFile = ideContextStore.get(); | |
| expect(activeFile).toEqual(secondFile); | |
| }); | |
| it('should handle empty string for file path', () => { | |
| const testFile = { | |
| workspaceState: { | |
| openFiles: [ | |
| { | |
| path: '', | |
| isActive: true, | |
| selectedText: '1234', | |
| timestamp: 0, | |
| }, | |
| ], | |
| }, | |
| }; | |
| ideContextStore.set(testFile); | |
| expect(ideContextStore.get()).toEqual(testFile); | |
| }); | |
| it('should notify subscribers when ide context changes', () => { | |
| const subscriber1 = vi.fn(); | |
| const subscriber2 = vi.fn(); | |
| ideContextStore.subscribe(subscriber1); | |
| ideContextStore.subscribe(subscriber2); | |
| const testFile = { | |
| workspaceState: { | |
| openFiles: [ | |
| { | |
| path: '/path/to/subscribed.ts', | |
| isActive: true, | |
| cursor: { line: 15, character: 25 }, | |
| timestamp: 0, | |
| }, | |
| ], | |
| }, | |
| }; | |
| ideContextStore.set(testFile); | |
| expect(subscriber1).toHaveBeenCalledTimes(1); | |
| expect(subscriber1).toHaveBeenCalledWith(testFile); | |
| expect(subscriber2).toHaveBeenCalledTimes(1); | |
| expect(subscriber2).toHaveBeenCalledWith(testFile); | |
| // Test with another update | |
| const newFile = { | |
| workspaceState: { | |
| openFiles: [ | |
| { | |
| path: '/path/to/new.js', | |
| isActive: true, | |
| selectedText: '1234', | |
| timestamp: 0, | |
| }, | |
| ], | |
| }, | |
| }; | |
| ideContextStore.set(newFile); | |
| expect(subscriber1).toHaveBeenCalledTimes(2); | |
| expect(subscriber1).toHaveBeenCalledWith(newFile); | |
| expect(subscriber2).toHaveBeenCalledTimes(2); | |
| expect(subscriber2).toHaveBeenCalledWith(newFile); | |
| }); | |
| it('should stop notifying a subscriber after unsubscribe', () => { | |
| const subscriber1 = vi.fn(); | |
| const subscriber2 = vi.fn(); | |
| const unsubscribe1 = ideContextStore.subscribe(subscriber1); | |
| ideContextStore.subscribe(subscriber2); | |
| ideContextStore.set({ | |
| workspaceState: { | |
| openFiles: [ | |
| { | |
| path: '/path/to/file1.txt', | |
| isActive: true, | |
| selectedText: '1234', | |
| timestamp: 0, | |
| }, | |
| ], | |
| }, | |
| }); | |
| expect(subscriber1).toHaveBeenCalledTimes(1); | |
| expect(subscriber2).toHaveBeenCalledTimes(1); | |
| unsubscribe1(); | |
| ideContextStore.set({ | |
| workspaceState: { | |
| openFiles: [ | |
| { | |
| path: '/path/to/file2.txt', | |
| isActive: true, | |
| selectedText: '1234', | |
| timestamp: 0, | |
| }, | |
| ], | |
| }, | |
| }); | |
| expect(subscriber1).toHaveBeenCalledTimes(1); // Should not be called again | |
| expect(subscriber2).toHaveBeenCalledTimes(2); | |
| }); | |
| it('should clear the ide context', () => { | |
| const testFile = { | |
| workspaceState: { | |
| openFiles: [ | |
| { | |
| path: '/path/to/test/file.ts', | |
| isActive: true, | |
| selectedText: '1234', | |
| timestamp: 0, | |
| }, | |
| ], | |
| }, | |
| }; | |
| ideContextStore.set(testFile); | |
| expect(ideContextStore.get()).toEqual(testFile); | |
| ideContextStore.clear(); | |
| expect(ideContextStore.get()).toBeUndefined(); | |
| }); | |
| it('should set the context and notify subscribers when no workspaceState is present', () => { | |
| const subscriber = vi.fn(); | |
| ideContextStore.subscribe(subscriber); | |
| const context: IdeContext = {}; | |
| ideContextStore.set(context); | |
| expect(ideContextStore.get()).toBe(context); | |
| expect(subscriber).toHaveBeenCalledWith(context); | |
| }); | |
| it('should handle an empty openFiles array', () => { | |
| const context: IdeContext = { | |
| workspaceState: { | |
| openFiles: [], | |
| }, | |
| }; | |
| ideContextStore.set(context); | |
| expect(ideContextStore.get()?.workspaceState?.openFiles).toEqual([]); | |
| }); | |
| it('should sort openFiles by timestamp in descending order', () => { | |
| const context: IdeContext = { | |
| workspaceState: { | |
| openFiles: [ | |
| { path: 'file1.ts', timestamp: 100, isActive: false }, | |
| { path: 'file2.ts', timestamp: 300, isActive: true }, | |
| { path: 'file3.ts', timestamp: 200, isActive: false }, | |
| ], | |
| }, | |
| }; | |
| ideContextStore.set(context); | |
| const openFiles = ideContextStore.get()?.workspaceState?.openFiles; | |
| expect(openFiles?.[0]?.path).toBe('file2.ts'); | |
| expect(openFiles?.[1]?.path).toBe('file3.ts'); | |
| expect(openFiles?.[2]?.path).toBe('file1.ts'); | |
| }); | |
| it('should mark only the most recent file as active and clear other active files', () => { | |
| const context: IdeContext = { | |
| workspaceState: { | |
| openFiles: [ | |
| { | |
| path: 'file1.ts', | |
| timestamp: 100, | |
| isActive: true, | |
| selectedText: 'hello', | |
| }, | |
| { | |
| path: 'file2.ts', | |
| timestamp: 300, | |
| isActive: true, | |
| cursor: { line: 1, character: 1 }, | |
| selectedText: 'hello', | |
| }, | |
| { | |
| path: 'file3.ts', | |
| timestamp: 200, | |
| isActive: false, | |
| selectedText: 'hello', | |
| }, | |
| ], | |
| }, | |
| }; | |
| ideContextStore.set(context); | |
| const openFiles = ideContextStore.get()?.workspaceState?.openFiles; | |
| expect(openFiles?.[0]?.isActive).toBe(true); | |
| expect(openFiles?.[0]?.cursor).toBeDefined(); | |
| expect(openFiles?.[0]?.selectedText).toBeDefined(); | |
| expect(openFiles?.[1]?.isActive).toBe(false); | |
| expect(openFiles?.[1]?.cursor).toBeUndefined(); | |
| expect(openFiles?.[1]?.selectedText).toBeUndefined(); | |
| expect(openFiles?.[2]?.isActive).toBe(false); | |
| expect(openFiles?.[2]?.cursor).toBeUndefined(); | |
| expect(openFiles?.[2]?.selectedText).toBeUndefined(); | |
| }); | |
| it('should truncate selectedText if it exceeds the max length', () => { | |
| const longText = 'a'.repeat(IDE_MAX_SELECTED_TEXT_LENGTH + 10); | |
| const context: IdeContext = { | |
| workspaceState: { | |
| openFiles: [ | |
| { | |
| path: 'file1.ts', | |
| timestamp: 100, | |
| isActive: true, | |
| selectedText: longText, | |
| }, | |
| ], | |
| }, | |
| }; | |
| ideContextStore.set(context); | |
| const selectedText = | |
| ideContextStore.get()?.workspaceState?.openFiles?.[0]?.selectedText; | |
| expect(selectedText).toHaveLength( | |
| IDE_MAX_SELECTED_TEXT_LENGTH + '... [TRUNCATED]'.length, | |
| ); | |
| expect(selectedText?.endsWith('... [TRUNCATED]')).toBe(true); | |
| }); | |
| it('should not truncate selectedText if it is within the max length', () => { | |
| const shortText = 'a'.repeat(IDE_MAX_SELECTED_TEXT_LENGTH); | |
| const context: IdeContext = { | |
| workspaceState: { | |
| openFiles: [ | |
| { | |
| path: 'file1.ts', | |
| timestamp: 100, | |
| isActive: true, | |
| selectedText: shortText, | |
| }, | |
| ], | |
| }, | |
| }; | |
| ideContextStore.set(context); | |
| const selectedText = | |
| ideContextStore.get()?.workspaceState?.openFiles?.[0]?.selectedText; | |
| expect(selectedText).toBe(shortText); | |
| }); | |
| it('should truncate the openFiles list if it exceeds the max length', () => { | |
| const files: File[] = Array.from( | |
| { length: IDE_MAX_OPEN_FILES + 5 }, | |
| (_, i) => ({ | |
| path: `file${i}.ts`, | |
| timestamp: i, | |
| isActive: false, | |
| }), | |
| ); | |
| const context: IdeContext = { | |
| workspaceState: { | |
| openFiles: files, | |
| }, | |
| }; | |
| ideContextStore.set(context); | |
| const openFiles = ideContextStore.get()?.workspaceState?.openFiles; | |
| expect(openFiles).toHaveLength(IDE_MAX_OPEN_FILES); | |
| }); | |
| }); | |
| describe('FileSchema', () => { | |
| it('should validate a file with only required fields', () => { | |
| const file = { | |
| path: '/path/to/file.ts', | |
| timestamp: 12345, | |
| }; | |
| const result = FileSchema.safeParse(file); | |
| expect(result.success).toBe(true); | |
| }); | |
| it('should validate a file with all fields', () => { | |
| const file = { | |
| path: '/path/to/file.ts', | |
| timestamp: 12345, | |
| isActive: true, | |
| selectedText: 'const x = 1;', | |
| cursor: { | |
| line: 10, | |
| character: 20, | |
| }, | |
| }; | |
| const result = FileSchema.safeParse(file); | |
| expect(result.success).toBe(true); | |
| }); | |
| it('should fail validation if path is missing', () => { | |
| const file = { | |
| timestamp: 12345, | |
| }; | |
| const result = FileSchema.safeParse(file); | |
| expect(result.success).toBe(false); | |
| }); | |
| it('should fail validation if timestamp is missing', () => { | |
| const file = { | |
| path: '/path/to/file.ts', | |
| }; | |
| const result = FileSchema.safeParse(file); | |
| expect(result.success).toBe(false); | |
| }); | |
| }); | |
| describe('IdeContextSchema', () => { | |
| it('should validate an empty context', () => { | |
| const context = {}; | |
| const result = IdeContextSchema.safeParse(context); | |
| expect(result.success).toBe(true); | |
| }); | |
| it('should validate a context with an empty workspaceState', () => { | |
| const context = { | |
| workspaceState: {}, | |
| }; | |
| const result = IdeContextSchema.safeParse(context); | |
| expect(result.success).toBe(true); | |
| }); | |
| it('should validate a context with an empty openFiles array', () => { | |
| const context = { | |
| workspaceState: { | |
| openFiles: [], | |
| }, | |
| }; | |
| const result = IdeContextSchema.safeParse(context); | |
| expect(result.success).toBe(true); | |
| }); | |
| it('should validate a context with a valid file', () => { | |
| const context = { | |
| workspaceState: { | |
| openFiles: [ | |
| { | |
| path: '/path/to/file.ts', | |
| timestamp: 12345, | |
| }, | |
| ], | |
| }, | |
| }; | |
| const result = IdeContextSchema.safeParse(context); | |
| expect(result.success).toBe(true); | |
| }); | |
| it('should fail validation with an invalid file', () => { | |
| const context = { | |
| workspaceState: { | |
| openFiles: [ | |
| { | |
| timestamp: 12345, // path is missing | |
| }, | |
| ], | |
| }, | |
| }; | |
| const result = IdeContextSchema.safeParse(context); | |
| expect(result.success).toBe(false); | |
| }); | |
| }); | |
| }); | |