Просмотр исходного кода

fix tests for useSWRxRef useSWRxRefs

Futa Arai 7 месяцев назад
Родитель
Сommit
635bebea84

+ 1 - 1
packages/remark-attachment-refs/package.json

@@ -41,7 +41,7 @@
     "lint:styles": "stylelint \"src/**/*.scss\" \"src/**/*.css\"",
     "lint:styles": "stylelint \"src/**/*.scss\" \"src/**/*.css\"",
     "lint:typecheck": "vue-tsc --noEmit",
     "lint:typecheck": "vue-tsc --noEmit",
     "lint": "run-p lint:*",
     "lint": "run-p lint:*",
-    "test": "vitest"
+    "test": "vitest run --coverage"
   },
   },
   "dependencies": {
   "dependencies": {
     "@growi/core": "workspace:^",
     "@growi/core": "workspace:^",

+ 359 - 0
packages/remark-attachment-refs/src/client/stores/refs.spec.ts

@@ -0,0 +1,359 @@
+import type { Server } from 'node:http';
+import { renderHook, waitFor } from '@testing-library/react';
+import axios from 'axios';
+import express from 'express';
+import refsMiddleware from '../../server';
+
+import { useSWRxRef, useSWRxRefs } from './refs';
+
+// Mock the IAttachmentHasId type for testing
+const mockAttachment = {
+  _id: '507f1f77bcf86cd799439011',
+  fileFormat: 'image/jpeg',
+  fileName: 'test-image.jpg',
+  originalName: 'test-image.jpg',
+  filePath: 'attachment/507f1f77bcf86cd799439011.jpg',
+  creator: {
+    _id: '507f1f77bcf86cd799439012',
+    name: 'Test User',
+    username: 'testuser',
+  },
+  page: '507f1f77bcf86cd799439013',
+  createdAt: '2023-01-01T00:00:00.000Z',
+  fileSize: 1024000,
+};
+
+// Mock PageQueryBuilder
+const mockPageQueryBuilder = {
+  addConditionToListWithDescendants: vi.fn().mockReturnThis(),
+  addConditionToExcludeTrashed: vi.fn().mockReturnThis(),
+  query: {
+    select: vi.fn().mockReturnValue({
+      exec: vi.fn().mockResolvedValue([{ id: '507f1f77bcf86cd799439013' }]),
+    }),
+    and: vi.fn().mockReturnThis(),
+  },
+};
+
+vi.mock('mongoose', async (importOriginal) => {
+  const actual = await importOriginal<typeof import('mongoose')>();
+  return {
+    ...actual,
+    default: {
+      ...actual.default,
+      model: vi.fn().mockImplementation((modelName) => {
+        const mockModel = {
+          findByPathAndViewer: vi.fn().mockResolvedValue({
+            _id: '507f1f77bcf86cd799439013',
+            path: '/test-page',
+          }),
+          isAccessiblePageByViewer: vi.fn().mockResolvedValue(true),
+          find: vi.fn().mockReturnValue({
+            select: vi.fn().mockReturnValue({
+              exec: vi
+                .fn()
+                .mockResolvedValue([{ id: '507f1f77bcf86cd799439013' }]),
+            }),
+            and: vi.fn().mockReturnThis(),
+          }),
+          addConditionToFilteringByViewerForList: vi.fn(),
+          PageQueryBuilder: vi
+            .fn()
+            .mockImplementation(() => mockPageQueryBuilder),
+        };
+
+        if (modelName === 'Attachment') {
+          return {
+            findOne: vi.fn().mockReturnValue({
+              populate: vi.fn().mockResolvedValue(mockAttachment),
+            }),
+            find: vi.fn().mockReturnValue({
+              and: vi.fn().mockReturnThis(),
+              populate: vi.fn().mockReturnThis(),
+              exec: vi.fn().mockResolvedValue([mockAttachment]),
+            }),
+          };
+        }
+
+        return mockModel;
+      }),
+    },
+    model: vi.fn().mockImplementation((modelName) => {
+      const mockModel = {
+        findByPathAndViewer: vi.fn().mockResolvedValue({
+          _id: '507f1f77bcf86cd799439013',
+          path: '/test-page',
+        }),
+        isAccessiblePageByViewer: vi.fn().mockResolvedValue(true),
+        find: vi.fn().mockReturnValue({
+          select: vi.fn().mockReturnValue({
+            exec: vi
+              .fn()
+              .mockResolvedValue([{ id: '507f1f77bcf86cd799439013' }]),
+          }),
+          and: vi.fn().mockReturnThis(),
+        }),
+        addConditionToFilteringByViewerForList: vi.fn(),
+        PageQueryBuilder: vi
+          .fn()
+          .mockImplementation(() => mockPageQueryBuilder),
+      };
+
+      if (modelName === 'Attachment') {
+        return {
+          findOne: vi.fn().mockReturnValue({
+            populate: vi.fn().mockResolvedValue(mockAttachment),
+          }),
+          find: vi.fn().mockReturnValue({
+            and: vi.fn().mockReturnThis(),
+            populate: vi.fn().mockReturnThis(),
+            exec: vi.fn().mockResolvedValue([mockAttachment]),
+          }),
+        };
+      }
+
+      return mockModel;
+    }),
+  };
+});
+
+// Mock @growi/core modules
+vi.mock('@growi/core', () => ({
+  SCOPE: {
+    READ: { FEATURES: { PAGE: 'read:page' } },
+  },
+}));
+
+vi.mock('@growi/core/dist/models/serializers', () => ({
+  serializeAttachmentSecurely: vi
+    .fn()
+    .mockImplementation((attachment) => attachment),
+}));
+
+vi.mock('@growi/core/dist/remark-plugins', () => ({
+  OptionParser: {
+    parseRange: vi.fn().mockReturnValue({ start: 1, end: 3 }),
+  },
+}));
+
+// Mock FilterXSS
+vi.mock('xss', () => ({
+  FilterXSS: vi.fn().mockImplementation(() => ({
+    process: vi.fn().mockImplementation((input) => input),
+  })),
+}));
+
+const TEST_PORT = 3002;
+const TEST_SERVER_URL = `http://localhost:${TEST_PORT}`;
+
+describe('useSWRxRef and useSWRxRefs integration tests', () => {
+  let server: Server;
+  let app: express.Application;
+
+  const setupAxiosSpy = () => {
+    const originalAxios = axios.create();
+    return vi.spyOn(axios, 'get').mockImplementation((url, config) => {
+      const fullUrl = url.startsWith('/_api')
+        ? `${TEST_SERVER_URL}${url}`
+        : url;
+      return originalAxios.get(fullUrl, config);
+    });
+  };
+
+  beforeAll(async () => {
+    app = express();
+    app.use(express.json());
+    app.use(express.urlencoded({ extended: true }));
+
+    app.use((req, res, next) => {
+      res.header('Access-Control-Allow-Origin', '*');
+      res.header(
+        'Access-Control-Allow-Methods',
+        'GET, POST, PUT, DELETE, OPTIONS',
+      );
+      res.header(
+        'Access-Control-Allow-Headers',
+        'Origin, X-Requested-With, Content-Type, Accept, Authorization',
+      );
+      next();
+    });
+
+    const mockCrowi = {
+      require: () => () => (req: any, res: any, next: any) => next(),
+      accessTokenParser: () => (req: any, res: any, next: any) => {
+        req.user = { _id: '507f1f77bcf86cd799439012', username: 'testuser' };
+        next();
+      },
+    };
+
+    refsMiddleware(mockCrowi, app);
+
+    return new Promise<void>((resolve) => {
+      server = app.listen(TEST_PORT, () => {
+        resolve();
+      });
+    });
+  });
+
+  afterAll(() => {
+    return new Promise<void>((resolve) => {
+      if (server) {
+        server.close(() => {
+          resolve();
+        });
+      } else {
+        resolve();
+      }
+    });
+  });
+
+  describe('useSWRxRef', () => {
+    it('should make actual server request and receive attachment data for single ref request', async () => {
+      const axiosGetSpy = setupAxiosSpy();
+
+      const { result } = renderHook(() =>
+        useSWRxRef('/test-page', 'test-image.jpg', false),
+      );
+
+      await waitFor(() => expect(result.current.data).toBeDefined(), {
+        timeout: 5000,
+      });
+
+      expect(axiosGetSpy).toHaveBeenCalledWith(
+        '/_api/attachment-refs/ref',
+        expect.objectContaining({
+          params: expect.objectContaining({
+            pagePath: '/test-page',
+            fileNameOrId: 'test-image.jpg',
+          }),
+        }),
+      );
+
+      expect(result.current.data).toBeDefined();
+      expect(result.current.error).toBeUndefined();
+
+      axiosGetSpy.mockRestore();
+    });
+
+    it('should handle ObjectId-based file reference correctly', async () => {
+      const axiosGetSpy = setupAxiosSpy();
+
+      const { result } = renderHook(() =>
+        useSWRxRef('/test-page', '507f1f77bcf86cd799439011', false),
+      );
+
+      await waitFor(() => expect(result.current.data).toBeDefined(), {
+        timeout: 5000,
+      });
+
+      expect(axiosGetSpy).toHaveBeenCalledWith(
+        '/_api/attachment-refs/ref',
+        expect.objectContaining({
+          params: expect.objectContaining({
+            pagePath: '/test-page',
+            fileNameOrId: '507f1f77bcf86cd799439011',
+          }),
+        }),
+      );
+
+      expect(result.current.data).toBeDefined();
+      expect(result.current.error).toBeUndefined();
+
+      axiosGetSpy.mockRestore();
+    });
+  });
+
+  describe('useSWRxRefs', () => {
+    it('should make actual server request and receive attachments data for refs request with pagePath', async () => {
+      const axiosGetSpy = setupAxiosSpy();
+
+      const { result } = renderHook(() =>
+        useSWRxRefs('/test-page', undefined, {}, false),
+      );
+
+      await waitFor(() => expect(result.current.data).toBeDefined(), {
+        timeout: 5000,
+      });
+
+      expect(axiosGetSpy).toHaveBeenCalledWith(
+        '/_api/attachment-refs/refs',
+        expect.objectContaining({
+          params: expect.objectContaining({
+            pagePath: '/test-page',
+            prefix: undefined,
+            options: {},
+          }),
+        }),
+      );
+
+      expect(result.current.data).toBeDefined();
+      expect(result.current.error).toBeUndefined();
+
+      axiosGetSpy.mockRestore();
+    });
+
+    it('should make actual server request and receive attachments data for refs request with prefix', async () => {
+      const axiosGetSpy = setupAxiosSpy();
+
+      const { result } = renderHook(() =>
+        useSWRxRefs('', '/test-prefix', { depth: '2' }, false),
+      );
+
+      await waitFor(() => expect(result.current.data).toBeDefined(), {
+        timeout: 5000,
+      });
+
+      expect(axiosGetSpy).toHaveBeenCalledWith(
+        '/_api/attachment-refs/refs',
+        expect.objectContaining({
+          params: expect.objectContaining({
+            pagePath: '',
+            prefix: '/test-prefix',
+            options: { depth: '2' },
+          }),
+        }),
+      );
+
+      expect(result.current.data).toBeDefined();
+      expect(result.current.error).toBeUndefined();
+
+      axiosGetSpy.mockRestore();
+    });
+
+    it('should handle different refs options correctly', async () => {
+      const axiosGetSpy = setupAxiosSpy();
+
+      const { result } = renderHook(() =>
+        useSWRxRefs(
+          '/parent-page',
+          undefined,
+          { regexp: 'test.*\\.jpg', depth: '3' },
+          false,
+        ),
+      );
+
+      await waitFor(() => expect(result.current.data).toBeDefined(), {
+        timeout: 5000,
+      });
+
+      expect(axiosGetSpy).toHaveBeenCalledWith(
+        '/_api/attachment-refs/refs',
+        expect.objectContaining({
+          params: expect.objectContaining({
+            pagePath: '/parent-page',
+            prefix: undefined,
+            options: expect.objectContaining({
+              regexp: 'test.*\\.jpg',
+              depth: '3',
+            }),
+          }),
+        }),
+      );
+
+      expect(result.current.data).toBeDefined();
+      expect(result.current.error).toBeUndefined();
+
+      axiosGetSpy.mockRestore();
+    });
+  });
+});

+ 0 - 297
packages/remark-attachment-refs/test/refs-integration.test.ts

@@ -1,297 +0,0 @@
-import { createServer, type Server } from 'node:http';
-import { renderHook, waitFor } from '@testing-library/react';
-import axios from 'axios';
-import express from 'express';
-import {
-  afterAll,
-  beforeAll,
-  beforeEach,
-  describe,
-  expect,
-  it,
-  vi,
-} from 'vitest';
-
-import { useSWRxRef, useSWRxRefs } from '../src/client/stores/refs';
-import { routesFactory } from '../src/server/routes/refs';
-
-// Test server setup
-let testServer: Server;
-let baseURL: string;
-
-// Mock data for testing
-const mockUser = {
-  _id: 'user123',
-  username: 'testuser',
-  email: 'test@example.com',
-};
-
-const mockPage = {
-  _id: 'page123',
-  path: '/test/page',
-};
-
-const mockAttachment = {
-  _id: 'attachment123',
-  originalName: 'test-file.pdf',
-  fileName: 'test-file.pdf',
-  fileFormat: 'pdf',
-  filePath: '/uploads/test-file.pdf',
-  filePathProxied: '/api/attachments/attachment123',
-  downloadPathProxied: '/api/attachments/attachment123/download',
-  fileSize: 1024,
-  attachmentType: 'file',
-  page: 'page123',
-  creator: 'user123',
-  createdAt: '2023-01-01T00:00:00.000Z', // Use string format to match JSON serialization
-};
-
-const mockAttachments = [
-  {
-    _id: 'attachment1',
-    originalName: 'file1.pdf',
-    fileName: 'file1.pdf',
-    filePath: '/uploads/file1.pdf',
-    creator: mockUser._id,
-    page: mockPage._id, // This should match the page ID returned by the page query
-    createdAt: '2023-01-01T00:00:00.000Z', // JSON serialized format
-    fileFormat: 'pdf',
-    fileSize: 1024,
-    attachmentType: 'file',
-    filePathProxied: '/api/attachments/attachment1',
-    downloadPathProxied: '/api/attachments/attachment1/download',
-  },
-  {
-    _id: 'attachment2',
-    originalName: 'file2.jpg',
-    fileName: 'file2.jpg',
-    filePath: '/uploads/file2.jpg',
-    creator: mockUser._id,
-    page: mockPage._id, // This should match the page ID returned by the page query
-    createdAt: '2023-01-01T00:00:00.000Z', // JSON serialized format
-    fileFormat: 'image',
-    fileSize: 2048,
-    attachmentType: 'image',
-    filePathProxied: '/api/attachments/attachment2',
-    downloadPathProxied: '/api/attachments/attachment2/download',
-  },
-];
-
-// Mock the Growi dependencies that the routes need
-const mockCrowi = {
-  require: vi.fn((path) => {
-    if (path === '../middlewares/login-required') {
-      return () => (_req, _res, next) => {
-        // Mock authentication - add user to request
-        _req.user = mockUser;
-        next();
-      };
-    }
-    return vi.fn();
-  }),
-  accessTokenParser: (_req, _res, next) => next(),
-};
-
-// Mock mongoose models
-const mockPageModel = {
-  findByPathAndViewer: vi.fn(),
-  isAccessiblePageByViewer: vi.fn().mockResolvedValue(true),
-  find: vi.fn().mockImplementation(() => {
-    // Return a mock query that has chaining methods
-    const mockQuery = {
-      select: vi.fn().mockReturnThis(),
-      exec: vi.fn().mockResolvedValue([
-        { id: mockPage._id }, // Return our mock page ID
-      ]),
-    };
-    return mockQuery;
-  }),
-  addConditionToFilteringByViewerForList: vi.fn(),
-  PageQueryBuilder: vi.fn().mockImplementation((query) => ({
-    query: query || mockPageModel.find(), // Use the actual query or fall back to find()
-    addConditionToListWithDescendants: vi.fn().mockReturnThis(),
-    addConditionToExcludeTrashed: vi.fn().mockReturnThis(),
-  })),
-};
-
-const mockAttachmentModel = {
-  findOne: vi.fn(),
-  find: vi.fn().mockImplementation(() => {
-    // Return a mock query that supports chaining
-    const mockQuery = {
-      and: vi.fn().mockReturnThis(),
-      populate: vi.fn().mockReturnThis(),
-      exec: vi.fn().mockResolvedValue(mockAttachments), // Return our mock attachments
-    };
-    return mockQuery;
-  }),
-  and: vi.fn().mockReturnThis(),
-  populate: vi.fn().mockReturnThis(),
-  exec: vi.fn().mockResolvedValue(mockAttachments),
-};
-
-// Mock mongoose
-vi.mock('mongoose', () => ({
-  default: {
-    model: vi.fn((modelName) => {
-      if (modelName === 'Page') return mockPageModel;
-      if (modelName === 'Attachment') return mockAttachmentModel;
-      return {};
-    }),
-  },
-  model: vi.fn((modelName) => {
-    if (modelName === 'Attachment') return mockAttachmentModel;
-    return {};
-  }),
-  Types: {
-    ObjectId: class MockObjectId {
-      private id: string;
-
-      constructor(id: string) {
-        this.id = id;
-      }
-
-      static isValid(_id: unknown): boolean {
-        return true; // Accept any ID as valid for testing
-      }
-
-      toString(): string {
-        return this.id;
-      }
-    },
-  },
-}));
-
-// Mock the serializer
-vi.mock('@growi/core/dist/models/serializers', () => ({
-  serializeAttachmentSecurely: vi.fn((attachment) => attachment),
-}));
-
-describe('refs hooks - Integration Tests with Actual Routes', () => {
-  beforeAll(async () => {
-    // Create a real Express app with the actual routes
-    const app = express();
-    app.use(express.json());
-    app.use(express.urlencoded({ extended: true }));
-
-    // Create the routes using the real routesFactory with our mocks
-    const router = routesFactory(mockCrowi);
-    app.use('/_api/attachment-refs', router);
-
-    // Start the test server on a dynamic port
-    testServer = createServer(app);
-
-    await new Promise<void>((resolve) => {
-      testServer.listen(0, () => {
-        const address = testServer.address();
-        if (address && typeof address === 'object') {
-          const port = address.port;
-          baseURL = `http://localhost:${port}`;
-          console.log(`Test server started on ${baseURL}`);
-        }
-        resolve();
-      });
-    });
-  });
-
-  afterAll(async () => {
-    // Clean up the test server
-    if (testServer) {
-      await new Promise<void>((resolve) => {
-        testServer.close(() => resolve());
-      });
-    }
-  });
-
-  beforeEach(() => {
-    // Reset all mocks before each test
-    vi.clearAllMocks();
-
-    // Setup default mock behaviors
-    mockPageModel.findByPathAndViewer.mockResolvedValue(mockPage);
-    mockPageModel.isAccessiblePageByViewer.mockResolvedValue(true);
-
-    mockAttachmentModel.findOne.mockImplementation(() => ({
-      populate: vi.fn().mockResolvedValue(mockAttachment),
-    }));
-
-    mockAttachmentModel.find.mockReturnValue({
-      and: vi.fn().mockReturnThis(),
-      populate: vi.fn().mockReturnThis(),
-      exec: vi.fn().mockResolvedValue(mockAttachments),
-    });
-  });
-
-  describe('SWR Hook Integration Tests', () => {
-    it('should test useSWRxRef hook with real server requests', async () => {
-      // Arrange
-      const pagePath = '/test/page';
-      const fileNameOrId = 'test-file.pdf';
-
-      // Configure axios to use our test server
-      const originalDefaults = axios.defaults.baseURL;
-      axios.defaults.baseURL = baseURL;
-
-      try {
-        console.log(`Testing with baseURL: ${baseURL}`);
-
-        // Act - Test the hook with real server
-        const { result } = renderHook(() =>
-          useSWRxRef(pagePath, fileNameOrId, false),
-        );
-
-        // Wait for the hook to complete the request
-        await waitFor(
-          () => {
-            console.log('Hook result:', result.current);
-            expect(result.current.data).toBeDefined();
-          },
-          { timeout: 5000 },
-        );
-
-        // Assert - Hook should return attachment data from real server
-        expect(result.current.data).toEqual(mockAttachment);
-        expect(result.current.error).toBeUndefined();
-      } finally {
-        // Restore original axios defaults
-        axios.defaults.baseURL = originalDefaults;
-      }
-    });
-
-    it('should test useSWRxRefs hook with real server requests', async () => {
-      // Arrange
-      const pagePath = '/test/page';
-      const prefix = '/test';
-      const options = { depth: '1', regexp: '.*\\.pdf$' };
-
-      // Configure axios to use our test server
-      const originalDefaults = axios.defaults.baseURL;
-      axios.defaults.baseURL = baseURL;
-
-      try {
-        console.log(`Testing with baseURL: ${baseURL}`);
-
-        // Act - Test the hook with real server
-        const { result } = renderHook(() =>
-          useSWRxRefs(pagePath, prefix, options, false),
-        );
-
-        // Wait for the hook to complete the request
-        await waitFor(
-          () => {
-            console.log('Hook result:', result.current);
-            expect(result.current.data).toBeDefined();
-          },
-          { timeout: 5000 },
-        );
-
-        // Assert - Hook should return attachments data from real server
-        expect(result.current.data).toEqual(mockAttachments);
-        expect(result.current.error).toBeUndefined();
-      } finally {
-        // Restore original axios defaults
-        axios.defaults.baseURL = originalDefaults;
-      }
-    });
-  });
-});

+ 0 - 0
packages/remark-attachment-refs/test/refs.test.ts


+ 0 - 10
packages/remark-attachment-refs/test/setup.ts

@@ -1,10 +0,0 @@
-// Test setup file for vitest
-
-// Mock console methods to avoid noise in tests
-global.console = {
-  ...console,
-  // Uncomment to mock console methods
-  // log: vi.fn(),
-  // warn: vi.fn(),
-  // error: vi.fn(),
-};

+ 1 - 0
packages/remark-attachment-refs/tsconfig.json

@@ -7,6 +7,7 @@
     "paths": {
     "paths": {
       "~/*": ["./src/*"]
       "~/*": ["./src/*"]
     },
     },
+    "types": ["vitest/globals"],
 
 
     /* TODO: remove below flags for strict checking */
     /* TODO: remove below flags for strict checking */
     "strict": false,
     "strict": false,

+ 6 - 2
packages/remark-attachment-refs/vitest.config.ts

@@ -4,9 +4,13 @@ import { defineConfig } from 'vitest/config';
 export default defineConfig({
 export default defineConfig({
   plugins: [tsconfigPaths()],
   plugins: [tsconfigPaths()],
   test: {
   test: {
-    environment: 'happy-dom', // React testing environment
+    environment: 'node',
     clearMocks: true,
     clearMocks: true,
     globals: true,
     globals: true,
-    setupFiles: ['./test/setup.ts'],
+    environmentMatchGlobs: [
+      // Use jsdom for client-side tests
+      ['**/client/**/*.spec.ts', 'jsdom'],
+      ['**/client/**/*.test.ts', 'jsdom'],
+    ],
   },
   },
 });
 });