import { getLokiCompletionProvider } from '../completion/getCompletionProvider'; import type { ProviderOptions } from '../completion/getCompletionProvider'; import * as monaco from 'monaco-editor'; // Mock internal modules jest.mock('../parser/locate', () => ({ parseAndLocate: jest.fn(), })); jest.mock('../parser/lexer', () => ({ offsetToPosition: jest.fn(), })); jest.mock('../completion/completions', () => ({ buildCompletions: jest.fn(), })); import { parseAndLocate } from '../parser/locate'; import { offsetToPosition } from '../parser/lexer'; import { buildCompletions } from '../completion/completions'; const mockParseAndLocate = parseAndLocate as jest.Mock; const mockOffsetToPosition = offsetToPosition as jest.Mock; const mockBuildCompletions = buildCompletions as jest.Mock; function createMockModel(id: string, value = ''): monaco.editor.ITextModel { return ({ id, getValue: () => value, getOffsetAt: jest.fn((pos) => pos.column - 1), } as unknown) as monaco.editor.ITextModel; } function createMockEditor(model: monaco.editor.ITextModel | null) { return ({ getModel: () => model, } as unknown) as monaco.editor.IStandaloneCodeEditor; } function createMockPosition(column: number, lineNumber = 1): monaco.Position { return { column, lineNumber } as monaco.Position; } beforeEach(() => { jest.clearAllMocks(); mockParseAndLocate.mockReturnValue({ ast: {}, situation: { kind: 'NONE', prefix: '', replaceRange: { start: 0, end: 0 } }, }); mockOffsetToPosition.mockReturnValue({ lineNumber: 1, column: 1 }); mockBuildCompletions.mockResolvedValue([]); }); describe('getLokiCompletionProvider - model ownership check', () => { it('returns empty suggestions when model ID does NOT match the editor model', async () => { const editorModel = createMockModel('editor-model-1', '{job="nginx"}'); const otherModel = createMockModel('other-model-2', 'some other content'); const editorRef = { current: createMockEditor(editorModel) }; const opts: ProviderOptions = { variablesRef: { current: [] }, fetchLabelNamesRef: { current: undefined }, fetchLabelValuesRef: { current: undefined }, editorRef, }; const provider = getLokiCompletionProvider(opts); const result = await provider.provideCompletionItems!(otherModel, createMockPosition(2)); expect(result).toEqual({ suggestions: [] }); // Internal functions should NOT be called when model doesn't match expect(mockParseAndLocate).not.toHaveBeenCalled(); expect(mockBuildCompletions).not.toHaveBeenCalled(); }); it('proceeds with completions when model ID matches the editor model', async () => { const model = createMockModel('same-model-1', '{job="nginx"}'); const editorRef = { current: createMockEditor(model) }; const opts: ProviderOptions = { variablesRef: { current: [] }, fetchLabelNamesRef: { current: undefined }, fetchLabelValuesRef: { current: undefined }, editorRef, }; mockBuildCompletions.mockResolvedValue([ { label: 'job', insertText: 'job' }, { label: 'app', insertText: 'app' }, ]); const provider = getLokiCompletionProvider(opts); const result = await provider.provideCompletionItems!(model, createMockPosition(2)); expect(result).not.toEqual({ suggestions: [] }); // Internal functions should be called expect(mockParseAndLocate).toHaveBeenCalled(); expect(mockBuildCompletions).toHaveBeenCalled(); }); it('returns empty suggestions when editorRef.current is null', async () => { const model = createMockModel('some-model', 'test'); const editorRef = { current: null }; const opts: ProviderOptions = { variablesRef: { current: [] }, fetchLabelNamesRef: { current: undefined }, fetchLabelValuesRef: { current: undefined }, editorRef, }; const provider = getLokiCompletionProvider(opts); const result = await provider.provideCompletionItems!(model, createMockPosition(1)); expect(result).toEqual({ suggestions: [] }); expect(mockParseAndLocate).not.toHaveBeenCalled(); }); it('returns empty suggestions when editor has no model', async () => { const model = createMockModel('some-model', 'test'); const editorRef = { current: createMockEditor(null) }; const opts: ProviderOptions = { variablesRef: { current: [] }, fetchLabelNamesRef: { current: undefined }, fetchLabelValuesRef: { current: undefined }, editorRef, }; const provider = getLokiCompletionProvider(opts); const result = await provider.provideCompletionItems!(model, createMockPosition(1)); expect(result).toEqual({ suggestions: [] }); expect(mockParseAndLocate).not.toHaveBeenCalled(); }); it('returns empty suggestions when editor model id is undefined', async () => { const editorModel = ({ id: undefined } as unknown) as monaco.editor.ITextModel; const model = createMockModel('some-model', 'test'); const editorRef = { current: createMockEditor(editorModel) }; const opts: ProviderOptions = { variablesRef: { current: [] }, fetchLabelNamesRef: { current: undefined }, fetchLabelValuesRef: { current: undefined }, editorRef, }; const provider = getLokiCompletionProvider(opts); const result = await provider.provideCompletionItems!(model, createMockPosition(1)); expect(result).toEqual({ suggestions: [] }); }); });