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

+ 1 - 0
apps/app/package.json

@@ -260,6 +260,7 @@
   },
   "devDependencies": {
     "@apidevtools/swagger-parser": "^10.1.1",
+    "@codemirror/state": "^6.5.2",
     "@emoji-mart/data": "^1.2.1",
     "@growi/core-styles": "workspace:^",
     "@growi/custom-icons": "workspace:^",

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

@@ -0,0 +1,72 @@
+import { Text } from '@codemirror/state';
+import type { UseCodeMirrorEditor } from '@growi/editor/dist/client/services/use-codemirror-editor';
+import {
+  describe,
+  it,
+  expect,
+  vi,
+  beforeEach,
+} from 'vitest';
+import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended';
+
+import { getPageBodyForContext } from './get-page-body-for-context';
+
+describe('getPageBodyForContext', () => {
+  let mockEditor: DeepMockProxy<UseCodeMirrorEditor>;
+
+  // Helper function to create identifiable content where each character shows its position
+  const createPositionalContent = (length: number): string => {
+    return Array.from({ length }, (_, i) => (i % 10).toString()).join('');
+  };
+
+  beforeEach(() => {
+    vi.clearAllMocks();
+    mockEditor = mockDeep<UseCodeMirrorEditor>();
+  });
+
+  it('should return undefined when editor is undefined', () => {
+    const result = getPageBodyForContext(undefined, 10, 10);
+    expect(result).toBeUndefined();
+  });
+
+  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 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
+    }
+  });
+});

+ 3 - 0
pnpm-lock.yaml

@@ -770,6 +770,9 @@ importers:
       '@apidevtools/swagger-parser':
         specifier: ^10.1.1
         version: 10.1.1(openapi-types@12.1.3)
+      '@codemirror/state':
+        specifier: ^6.5.2
+        version: 6.5.2
       '@emoji-mart/data':
         specifier: ^1.2.1
         version: 1.2.1