Yuki Takei 7 месяцев назад
Родитель
Сommit
03c06bc469
1 измененных файлов с 65 добавлено и 0 удалено
  1. 65 0
      apps/app/src/pages/[[...path]]/use-same-route-navigation.spec.tsx

+ 65 - 0
apps/app/src/pages/[[...path]]/use-same-route-navigation.spec.tsx

@@ -164,5 +164,70 @@ describe('useSameRouteNavigation - Essential Bug Fix Verification', () => {
       // Should not fetch when everything is already in sync
       expect(mockFetchCurrentPage).not.toHaveBeenCalled();
     });
+
+    // CRITICAL: Race condition prevention - THE CORE BUG FIX
+    it('should prevent concurrent fetches during rapid navigation', async() => {
+      const props = createProps('/first/page');
+      mockRouter.asPath = '/first/page';
+
+      // Simulate slow network request
+      let resolveFetch: (value: { revision: { body: string } }) => void = () => {};
+      const slowFetchPromise = new Promise((resolve) => {
+        resolveFetch = resolve;
+      });
+      mockFetchCurrentPage.mockReturnValue(slowFetchPromise);
+
+      const { rerender } = renderHook(() => useSameRouteNavigation(props, extractPageIdFromPathname, isInitialProps));
+
+      // Start first fetch
+      await act(async() => {
+        await new Promise(resolve => setTimeout(resolve, 0));
+      });
+      expect(mockFetchCurrentPage).toHaveBeenCalledTimes(1);
+
+      // Rapid navigation - should be prevented
+      mockRouter.asPath = '/second/page';
+      rerender();
+
+      // Should NOT start second fetch due to race condition protection
+      expect(mockFetchCurrentPage).toHaveBeenCalledTimes(1); // Still only 1 call
+
+      // Complete the first fetch
+      resolveFetch({ revision: { body: 'content' } });
+      await act(async() => {
+        await slowFetchPromise;
+      });
+
+      // Now second navigation should work
+      mockRouter.asPath = '/third/page';
+      rerender();
+
+      await act(async() => {
+        await new Promise(resolve => setTimeout(resolve, 0));
+      });
+
+      expect(mockFetchCurrentPage).toHaveBeenCalledTimes(2); // Now second call
+    });
+
+    // CRITICAL: State clearing sequence - prevents stale data
+    it('should clear pageId before setting new one', async() => {
+      const props = createProps('/new/page');
+      mockRouter.asPath = '/new/page';
+
+      // Start with existing page
+      mockUseCurrentPageId.mockReturnValue(['old-page-id', mockSetCurrentPageId]);
+
+      renderHook(() => useSameRouteNavigation(props, extractPageIdFromPathname, isInitialProps));
+
+      await act(async() => {
+        await new Promise(resolve => setTimeout(resolve, 0));
+      });
+
+      // Verify clearing sequence: undefined first, then new ID
+      expect(mockSetCurrentPageId).toHaveBeenCalledWith(undefined);
+      expect(mockSetCurrentPageId).toHaveBeenCalledWith('id-for--new-page');
+      expect(mockSetCurrentPageId.mock.calls[0][0]).toBe(undefined);
+      expect(mockSetCurrentPageId.mock.calls[1][0]).toBe('id-for--new-page');
+    });
   });
 });