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

suppress the warning "An update to TestComponent inside a test was not wrapped in act(...)"

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

+ 31 - 11
apps/app/src/features/page-tree/components/ItemsTree.spec.tsx

@@ -1,7 +1,7 @@
 import type React from 'react';
 import type { FC } from 'react';
 import { Suspense } from 'react';
-import { render, waitFor } from '@testing-library/react';
+import { act, render, waitFor } from '@testing-library/react';
 
 import type { IPageForTreeItem } from '~/interfaces/page';
 
@@ -179,7 +179,9 @@ describe('ItemsTree', () => {
       });
 
       // Give time for any additional API calls that might happen
-      await new Promise((resolve) => setTimeout(resolve, 100));
+      await act(async () => {
+        await new Promise((resolve) => setTimeout(resolve, 100));
+      });
 
       // Key assertion: API should only be called for:
       // 1. root-page-id (the only expanded node by default)
@@ -241,7 +243,9 @@ describe('ItemsTree', () => {
         expect(mockApiv3Get).toHaveBeenCalled();
       });
 
-      await new Promise((resolve) => setTimeout(resolve, 100));
+      await act(async () => {
+        await new Promise((resolve) => setTimeout(resolve, 100));
+      });
 
       const childrenApiCalls = mockApiv3Get.mock.calls.filter(
         (call) => call[0] === '/page-listing/children',
@@ -306,7 +310,9 @@ describe('ItemsTree', () => {
       });
 
       // Wait for any potential additional API calls
-      await new Promise((resolve) => setTimeout(resolve, 200));
+      await act(async () => {
+        await new Promise((resolve) => setTimeout(resolve, 200));
+      });
 
       // Critical assertion: Only root-page-id should have children fetched
       // folder-1 and folder-2 should NOT be fetched even though they are folders (descendantCount > 0)
@@ -385,7 +391,9 @@ describe('ItemsTree', () => {
       );
 
       // Give some extra time for any unwanted calls
-      await new Promise((resolve) => setTimeout(resolve, 200));
+      await act(async () => {
+        await new Promise((resolve) => setTimeout(resolve, 200));
+      });
 
       // Should fetch: root-page-id (initial), sandbox-id (ancestor of target)
       // Should NOT fetch: other-id (not an ancestor of target)
@@ -469,7 +477,9 @@ describe('ItemsTree', () => {
 
       // Wait a reasonable amount of time to detect infinite loops
       // If there's an infinite loop, we'd see many API calls within this time
-      await new Promise((resolve) => setTimeout(resolve, 500));
+      await act(async () => {
+        await new Promise((resolve) => setTimeout(resolve, 500));
+      });
 
       // Force re-render to simulate React re-renders that could trigger the loop
       rerender(
@@ -485,7 +495,9 @@ describe('ItemsTree', () => {
       );
 
       // Wait more time for potential infinite loop to manifest
-      await new Promise((resolve) => setTimeout(resolve, 500));
+      await act(async () => {
+        await new Promise((resolve) => setTimeout(resolve, 500));
+      });
 
       // Key assertion: API calls for parent-1 should be bounded
       // An infinite loop would cause this count to be very high (100+)
@@ -555,7 +567,9 @@ describe('ItemsTree', () => {
       await waitFor(() => {
         expect(mockApiv3Get).toHaveBeenCalled();
       });
-      await new Promise((resolve) => setTimeout(resolve, 200));
+      await act(async () => {
+        await new Promise((resolve) => setTimeout(resolve, 200));
+      });
 
       const callsAfterInitialLoad = totalApiCalls;
 
@@ -564,7 +578,9 @@ describe('ItemsTree', () => {
       // we're mainly testing the initial render with creatingParentId set
 
       // Wait to ensure no more calls happen
-      await new Promise((resolve) => setTimeout(resolve, 500));
+      await act(async () => {
+        await new Promise((resolve) => setTimeout(resolve, 500));
+      });
 
       // Verify API calls stabilized
       expect(totalApiCalls).toBeLessThanOrEqual(callsAfterInitialLoad + 2);
@@ -624,7 +640,9 @@ describe('ItemsTree', () => {
       await waitFor(() => {
         expect(mockApiv3Get).toHaveBeenCalled();
       });
-      await new Promise((resolve) => setTimeout(resolve, 300));
+      await act(async () => {
+        await new Promise((resolve) => setTimeout(resolve, 300));
+      });
 
       const callsBeforeReset = apiCallCount;
 
@@ -647,7 +665,9 @@ describe('ItemsTree', () => {
         </TestWrapper>,
       );
 
-      await new Promise((resolve) => setTimeout(resolve, 500));
+      await act(async () => {
+        await new Promise((resolve) => setTimeout(resolve, 500));
+      });
 
       // API calls should be bounded even after state changes
       // The difference should be minimal (just the initial load after remount)

+ 98 - 39
apps/app/src/states/page/use-fetch-current-page.spec.tsx

@@ -8,7 +8,7 @@ import type {
   PageGrant,
   PageStatus,
 } from '@growi/core/dist/interfaces';
-import { renderHook, waitFor } from '@testing-library/react';
+import { act, renderHook, waitFor } from '@testing-library/react';
 // biome-ignore lint/style/noRestrictedImports: import only types
 import type { AxiosResponse } from 'axios';
 import { createStore, Provider } from 'jotai';
@@ -180,7 +180,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
     // Act
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ path: '/new/page' });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ path: '/new/page' });
+    });
 
     // Assert: Wait for state updates
     await waitFor(() => {
@@ -211,7 +213,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
     // Act
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ path: '/same/path' });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ path: '/same/path' });
+    });
 
     // Assert
     // Use a short timeout to ensure no fetch is initiated
@@ -231,7 +235,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
     // Act
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ pageId: 'page123' });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ pageId: 'page123' });
+    });
 
     // Assert
     await new Promise((resolve) => setTimeout(resolve, 100));
@@ -261,9 +267,11 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
     // Act
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({
-      path: '/same/path',
-      revisionId: 'rev_different',
+    await act(async () => {
+      await result.current.fetchCurrentPage({
+        path: '/same/path',
+        revisionId: 'rev_different',
+      });
     });
 
     // Assert
@@ -294,9 +302,11 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
     // Act
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({
-      path: '/same/path',
-      revisionId: currentRevisionId,
+    await act(async () => {
+      await result.current.fetchCurrentPage({
+        path: '/same/path',
+        revisionId: currentRevisionId,
+      });
     });
 
     // Assert
@@ -327,9 +337,11 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
     // Act
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({
-      path: '/same/path',
-      revisionId: 'rev_old',
+    await act(async () => {
+      await result.current.fetchCurrentPage({
+        path: '/same/path',
+        revisionId: 'rev_old',
+      });
     });
 
     // Assert
@@ -367,7 +379,12 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
     // Act
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ path: '/same/path', force: true });
+    await act(async () => {
+      await result.current.fetchCurrentPage({
+        path: '/same/path',
+        force: true,
+      });
+    });
 
     // Assert
     await waitFor(() => {
@@ -405,7 +422,12 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
     // Act
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ pageId: 'pageId123', force: true });
+    await act(async () => {
+      await result.current.fetchCurrentPage({
+        pageId: 'pageId123',
+        force: true,
+      });
+    });
 
     // Assert
     await waitFor(() => {
@@ -444,9 +466,11 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
     // Act
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({
-      path: `/${permalinkId}`,
-      force: true,
+    await act(async () => {
+      await result.current.fetchCurrentPage({
+        path: `/${permalinkId}`,
+        force: true,
+      });
     });
 
     // Assert
@@ -475,7 +499,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
     mockedApiv3Get.mockResolvedValue(mockApiResponse(regularPageData));
 
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ path: '/some/page' });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ path: '/some/page' });
+    });
 
     await waitFor(() => {
       expect(store.get(currentPageEntityIdAtom)).toBe('regularPageId');
@@ -491,7 +517,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
     mockedApiv3Get.mockResolvedValue(mockApiResponse(rootPageData));
 
     // Act
-    await result.current.fetchCurrentPage({ path: '/' });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ path: '/' });
+    });
 
     // Assert: Navigation to root works
     await waitFor(() => {
@@ -516,7 +544,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
     // Act
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ path: encodedPath });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ path: encodedPath });
+    });
 
     // Assert
     await waitFor(() => {
@@ -540,7 +570,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
     // Act
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ path: permalink });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ path: permalink });
+    });
 
     // Assert
     await waitFor(() => {
@@ -567,7 +599,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
     // Act
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ path: permalinkPath });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ path: permalinkPath });
+    });
 
     // Assert
     await waitFor(() => {
@@ -604,9 +638,11 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
     // Act
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({
-      path: permalinkPath,
-      pageId: explicitPageId,
+    await act(async () => {
+      await result.current.fetchCurrentPage({
+        path: permalinkPath,
+        pageId: explicitPageId,
+      });
     });
 
     // Assert
@@ -640,7 +676,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
     // Act
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ path: regularPath });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ path: regularPath });
+    });
 
     // Assert
     await waitFor(() => {
@@ -674,7 +712,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
     // Act
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ path: permalinkWithHash });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ path: permalinkWithHash });
+    });
 
     // Assert
     await waitFor(() => {
@@ -733,7 +773,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
       // Act
       const { result } = renderHookWithProvider();
-      await result.current.fetchCurrentPage({ path: testCase.input });
+      await act(async () => {
+        await result.current.fetchCurrentPage({ path: testCase.input });
+      });
 
       // Assert
       await waitFor(() => {
@@ -769,7 +811,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
     mockedApiv3Get.mockRejectedValueOnce([notFoundError]);
 
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ path: '/will/not/found' });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ path: '/will/not/found' });
+    });
 
     // Assert
     await waitFor(() => {
@@ -794,7 +838,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
     mockedApiv3Get.mockRejectedValueOnce(unknownError);
 
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ path: '/any/path' });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ path: '/any/path' });
+    });
 
     await waitFor(() => {
       const err = store.get(pageErrorAtom);
@@ -813,7 +859,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
     mockedApiv3Get.mockRejectedValueOnce([]);
 
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ path: '/any/path' });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ path: '/any/path' });
+    });
 
     await waitFor(() => {
       const err = store.get(pageErrorAtom);
@@ -836,7 +884,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
     mockedApiv3Get.mockRejectedValueOnce([nonErrorV3]);
 
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ path: '/any/path' });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ path: '/any/path' });
+    });
 
     await waitFor(() => {
       expect(store.get(pageLoadingAtom)).toBe(false);
@@ -870,7 +920,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
     mockedApiv3Get.mockResolvedValueOnce(notFoundResponseWithEmptyPage);
 
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ path: '/empty/page' });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ path: '/empty/page' });
+    });
 
     // Assert: emptyPageId should be set from meta
     await waitFor(() => {
@@ -902,7 +954,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
     mockedApiv3Get.mockResolvedValueOnce(notFoundResponseWithoutEmptyPage);
 
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ path: '/regular/not/found' });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ path: '/regular/not/found' });
+    });
 
     // Assert: emptyPageId should be undefined
     await waitFor(() => {
@@ -930,7 +984,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
     // Act
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ path: '/success/path' });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ path: '/success/path' });
+    });
 
     // Assert: emptyPageId should be reset to undefined
     await waitFor(() => {
@@ -945,7 +1001,6 @@ describe('useFetchCurrentPage - Integration Test', () => {
   it('should handle path with encoded Japanese characters', async () => {
     // Arrange: Path with Japanese characters
     const japanesePath = '/日本語/ページ';
-    const encodedPath = encodeURIComponent(japanesePath);
     const pageData = createPageDataMock(
       'japanesePageId',
       japanesePath,
@@ -955,7 +1010,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
     // Act
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ path: japanesePath });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ path: japanesePath });
+    });
 
     // Assert: Path should be properly decoded and sent to API
     await waitFor(() => {
@@ -978,7 +1035,9 @@ describe('useFetchCurrentPage - Integration Test', () => {
 
     // Act
     const { result } = renderHookWithProvider();
-    await result.current.fetchCurrentPage({ path: '/test/path' });
+    await act(async () => {
+      await result.current.fetchCurrentPage({ path: '/test/path' });
+    });
 
     // Assert: mutatePageInfo should be called to refetch metadata
     await waitFor(() => {