Yuki Takei 9 месяцев назад
Родитель
Сommit
19e8176afe

+ 170 - 37
apps/app/src/features/openai/client/services/editor-assistant/get-page-body-for-context.spec.ts

@@ -24,49 +24,182 @@ describe('getPageBodyForContext', () => {
     mockEditor = mockDeep<UseCodeMirrorEditor>();
     mockEditor = mockDeep<UseCodeMirrorEditor>();
   });
   });
 
 
-  it('should return undefined when editor is undefined', () => {
-    const result = getPageBodyForContext(undefined, 10, 10);
-    expect(result).toBeUndefined();
+  describe('Error handling and edge cases', () => {
+    it('should return undefined when editor is undefined', () => {
+      const result = getPageBodyForContext(undefined, 10, 10);
+      expect(result).toBeUndefined();
+    });
+
+    it('should handle missing view state (defaults cursor to 0)', () => {
+      const longContent = createPositionalContent(1000);
+      const realDoc = Text.of([longContent]);
+
+      mockEditor.getDoc.mockReturnValue(realDoc);
+      mockEditor.view = undefined;
+
+      const result = getPageBodyForContext(mockEditor, 100, 200);
+
+      // Should default cursor position to 0 and take 200 chars after
+      const expectedContent = longContent.slice(0, 200);
+      expect(result).toBe(expectedContent);
+      expect(result).toHaveLength(200);
+    });
   });
   });
 
 
-  it('should return getDocString when document is short', () => {
-    // Create a real Text instance with short content
-    const shortText = 'short';
-    const realDoc = Text.of([shortText]); // length: 5, shorter than maxTotalLength of 20
+  describe('Short document handling', () => {
+    it('should return getDocString when document is short', () => {
+      // Create a real Text instance with short content
+      const shortText = 'short';
+      const realDoc = Text.of([shortText]); // length: 5, shorter than maxTotalLength of 20
+
+      mockEditor.getDoc.mockReturnValue(realDoc);
+      mockEditor.getDocString.mockReturnValue(shortText);
+
+      const result = getPageBodyForContext(mockEditor, 10, 10);
+
+      expect(result).toBe(shortText);
+      expect(mockEditor.getDocString).toHaveBeenCalled();
+    });
+
+    it('should return full document when length equals max total length', () => {
+      const exactLengthText = createPositionalContent(150); // exactly 150 chars
+      const realDoc = Text.of([exactLengthText]);
+
+      mockEditor.getDoc.mockReturnValue(realDoc);
+      mockEditor.getDocString.mockReturnValue(exactLengthText);
+
+      const result = getPageBodyForContext(mockEditor, 50, 100); // total: 150
 
 
-    mockEditor.getDoc.mockReturnValue(realDoc);
-    mockEditor.getDocString.mockReturnValue(shortText);
+      expect(result).toBe(exactLengthText);
+      expect(mockEditor.getDocString).toHaveBeenCalled();
+    });
 
 
-    const result = getPageBodyForContext(mockEditor, 10, 10);
+    it('should return full document when length is less than max total length', () => {
+      const shortText = 'Short document'; // 14 chars
+      const realDoc = Text.of([shortText]);
 
 
-    expect(result).toBe(shortText);
-    expect(mockEditor.getDocString).toHaveBeenCalled();
+      mockEditor.getDoc.mockReturnValue(realDoc);
+      mockEditor.getDocString.mockReturnValue(shortText);
+
+      const result = getPageBodyForContext(mockEditor, 50, 100); // total: 150
+
+      expect(result).toBe(shortText);
+    });
   });
   });
 
 
-  it('should extract text around cursor when document is long', () => {
-    // Create a real Text instance with identifiable content (each character shows its position)
-    const longContent = createPositionalContent(1000);
-    // Content: "0123456789012345678901234567890123456789..." (position-based)
-    const realDoc = Text.of([longContent]); // length: 1000, longer than maxTotalLength of 300
-
-    mockEditor.getDoc.mockReturnValue(realDoc);
-
-    // Mock view with cursor at position 500
-    if (mockEditor.view?.state?.selection?.main) {
-      Object.defineProperty(mockEditor.view.state.selection.main, 'head', { value: 500 });
-    }
-
-    const result = getPageBodyForContext(mockEditor, 100, 200);
-
-    // Expected: slice(400, 700) should extract characters from position 400 to 699
-    // Position 400: '0' (400 % 10), Position 699: '9' (699 % 10)
-    const expectedContent = longContent.slice(400, 700);
-    expect(result).toBe(expectedContent);
-    expect(result).toHaveLength(300); // 700 - 400 = 300
-    expect(result).toBeDefined();
-    if (result) {
-      expect(result[0]).toBe('0'); // First character at position 400
-      expect(result[299]).toBe('9'); // Last character at position 699
-    }
+  describe('Core shortfall compensation logic', () => {
+    it('should extract correct range when cursor is in middle (no shortfall)', () => {
+      const longContent = createPositionalContent(2000);
+      const realDoc = Text.of([longContent]);
+      const cursorPos = 1000;
+
+      mockEditor.getDoc.mockReturnValue(realDoc);
+
+      // Mock view with cursor at position 1000
+      if (mockEditor.view?.state?.selection?.main) {
+        Object.defineProperty(mockEditor.view.state.selection.main, 'head', { value: cursorPos });
+      }
+
+      const result = getPageBodyForContext(mockEditor, 200, 300);
+
+      // Expected: start=800, end=1300 (no shortfall needed)
+      const expectedContent = longContent.slice(800, 1300);
+      expect(result).toBe(expectedContent);
+      expect(result).toHaveLength(500); // 1300 - 800 = 500
+    });
+
+    it('should compensate shortfall when cursor is near document end', () => {
+      const longContent = createPositionalContent(1000);
+      const realDoc = Text.of([longContent]);
+      const cursorPos = 950; // Near end
+
+      mockEditor.getDoc.mockReturnValue(realDoc);
+
+      // Mock view with cursor at position 950
+      if (mockEditor.view?.state?.selection?.main) {
+        Object.defineProperty(mockEditor.view.state.selection.main, 'head', { value: cursorPos });
+      }
+
+      const result = getPageBodyForContext(mockEditor, 100, 200);
+
+      // Available after cursor: 1000 - 950 = 50
+      // Shortfall: 200 - 50 = 150
+      // Chars before: 100 + 150 = 250
+      // Expected: start=max(0, 950-250)=700, end=950+50=1000
+      const expectedContent = longContent.slice(700, 1000);
+      expect(result).toBe(expectedContent);
+      expect(result).toHaveLength(300); // 1000 - 700 = 300
+    });
+
+    it('should handle extreme case: cursor at document end', () => {
+      const longContent = createPositionalContent(1000);
+      const realDoc = Text.of([longContent]);
+      const cursorPos = 1000; // At very end
+
+      mockEditor.getDoc.mockReturnValue(realDoc);
+
+      // Mock view with cursor at position 1000
+      if (mockEditor.view?.state?.selection?.main) {
+        Object.defineProperty(mockEditor.view.state.selection.main, 'head', { value: cursorPos });
+      }
+
+      const result = getPageBodyForContext(mockEditor, 100, 200);
+
+      // Available after cursor: 0
+      // Shortfall: 200 - 0 = 200
+      // Chars before: 100 + 200 = 300
+      // Expected: start=max(0, 1000-300)=700, end=1000+0=1000
+      const expectedContent = longContent.slice(700, 1000);
+      expect(result).toBe(expectedContent);
+      expect(result).toHaveLength(300); // 1000 - 700 = 300
+    });
+
+    it('should handle cursor at document start with startPos boundary', () => {
+      const longContent = createPositionalContent(1000);
+      const realDoc = Text.of([longContent]);
+      const cursorPos = 0; // At start
+
+      mockEditor.getDoc.mockReturnValue(realDoc);
+
+      // Mock view with cursor at position 0
+      if (mockEditor.view?.state?.selection?.main) {
+        Object.defineProperty(mockEditor.view.state.selection.main, 'head', { value: cursorPos });
+      }
+
+      const result = getPageBodyForContext(mockEditor, 100, 200);
+
+      // Available after cursor: 1000
+      // Chars after: min(200, 1000) = 200
+      // Shortfall: 200 - 200 = 0
+      // Chars before: 100 + 0 = 100
+      // Expected: start=max(0, 0-100)=0, end=0+200=200
+      const expectedContent = longContent.slice(0, 200);
+      expect(result).toBe(expectedContent);
+      expect(result).toHaveLength(200);
+    });
+
+    it('should handle truly extreme shortfall with cursor very near end', () => {
+      const longContent = createPositionalContent(1000);
+      const realDoc = Text.of([longContent]);
+      const cursorPos = 995; // Very near end
+
+      mockEditor.getDoc.mockReturnValue(realDoc);
+
+      // Mock view with cursor at position 995
+      if (mockEditor.view?.state?.selection?.main) {
+        Object.defineProperty(mockEditor.view.state.selection.main, 'head', { value: cursorPos });
+      }
+
+      const result = getPageBodyForContext(mockEditor, 50, 500); // Total: 550 < 1000
+
+      // Available after cursor: 1000 - 995 = 5
+      // Shortfall: 500 - 5 = 495
+      // Chars before: 50 + 495 = 545
+      // Expected: start=max(0, 995-545)=450, end=995+5=1000
+      const expectedContent = longContent.slice(450, 1000);
+      expect(result).toBe(expectedContent);
+      expect(result).toHaveLength(550); // 1000 - 450 = 550
+    });
+
   });
   });
 });
 });