Browse Source

add `force: true` option

Yuki Takei 5 months ago
parent
commit
c04434d878

+ 1 - 1
apps/app/src/client/components/Bookmarks/BookmarkItem.tsx

@@ -146,7 +146,7 @@ export const BookmarkItem = (props: Props): JSX.Element => {
         mutateAllPageInfo();
         mutateAllPageInfo();
         bookmarkFolderTreeMutation();
         bookmarkFolderTreeMutation();
         router.push(`/${pageId}`);
         router.push(`/${pageId}`);
-        fetchCurrentPage();
+        fetchCurrentPage({ force: true });
         toastSuccess(t('page_has_been_reverted', { path }));
         toastSuccess(t('page_has_been_reverted', { path }));
       }
       }
       catch (err) {
       catch (err) {

+ 2 - 2
apps/app/src/client/components/ItemsTree/ItemsTree.tsx

@@ -88,7 +88,7 @@ export const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
     mutatePageList();
     mutatePageList();
 
 
     if (currentPagePath === fromPath || currentPagePath === toPath) {
     if (currentPagePath === fromPath || currentPagePath === toPath) {
-      fetchCurrentPage();
+      fetchCurrentPage({ force: true });
     }
     }
   }, [currentPagePath, fetchCurrentPage]);
   }, [currentPagePath, fetchCurrentPage]);
 
 
@@ -124,7 +124,7 @@ export const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
       mutateAllPageInfo();
       mutateAllPageInfo();
 
 
       if (currentPagePath === pathOrPathsToDelete) {
       if (currentPagePath === pathOrPathsToDelete) {
-        fetchCurrentPage();
+        fetchCurrentPage({ force: true });
         router.push(isCompletely ? path.dirname(pathOrPathsToDelete) : `/trash${pathOrPathsToDelete}`);
         router.push(isCompletely ? path.dirname(pathOrPathsToDelete) : `/trash${pathOrPathsToDelete}`);
       }
       }
     };
     };

+ 2 - 2
apps/app/src/client/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -305,7 +305,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
 
 
   const renameItemClickedHandler = useCallback(async (page: IPageToRenameWithMeta<IPageInfoForEntity>) => {
   const renameItemClickedHandler = useCallback(async (page: IPageToRenameWithMeta<IPageInfoForEntity>) => {
     const renamedHandler: OnRenamedFunction = () => {
     const renamedHandler: OnRenamedFunction = () => {
-      fetchCurrentPage();
+      fetchCurrentPage({ force: true });
       mutatePageInfo();
       mutatePageInfo();
       mutatePageTree();
       mutatePageTree();
       mutateRecentlyUpdated();
       mutateRecentlyUpdated();
@@ -329,7 +329,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
         router.push(currentPathname);
         router.push(currentPathname);
       }
       }
 
 
-      fetchCurrentPage();
+      fetchCurrentPage({ force: true });
       mutatePageInfo();
       mutatePageInfo();
       mutatePageTree();
       mutatePageTree();
       mutateRecentlyUpdated();
       mutateRecentlyUpdated();

+ 1 - 1
apps/app/src/client/components/PageEditor/page-path-rename-utils.ts

@@ -37,7 +37,7 @@ export const usePagePathRenameHandler = (
       setIsUntitledPage(false);
       setIsUntitledPage(false);
 
 
       if (currentPage.path === fromPath || currentPage.path === toPath) {
       if (currentPage.path === fromPath || currentPage.path === toPath) {
-        fetchCurrentPage();
+        fetchCurrentPage({ force: true });
       }
       }
     };
     };
 
 

+ 1 - 1
apps/app/src/client/services/page-operation.ts

@@ -107,7 +107,7 @@ export const useUpdateStateAfterSave = (pageId: string|undefined|null, opts?: Up
   return useCallback(async() => {
   return useCallback(async() => {
     if (pageId == null) { return }
     if (pageId == null) { return }
 
 
-    const updatedPage = await fetchCurrentPage({ pageId });
+    const updatedPage = await fetchCurrentPage({ pageId, force: true });
 
 
     if (updatedPage == null || updatedPage.revision == null) { return }
     if (updatedPage == null || updatedPage.revision == null) { return }
 
 

+ 1 - 1
apps/app/src/components/PageView/PageAlerts/OldRevisionAlert.tsx

@@ -24,7 +24,7 @@ export const OldRevisionAlert = (): JSX.Element => {
 
 
     const url = returnPathForURL(page.path, page._id);
     const url = returnPathForURL(page.path, page._id);
     await router.push(url);
     await router.push(url);
-    fetchCurrentPage();
+    fetchCurrentPage({ force: true });
   }, [fetchCurrentPage, page, router]);
   }, [fetchCurrentPage, page, router]);
 
 
   if (page == null || isOldRevisionPage) {
   if (page == null || isOldRevisionPage) {

+ 1 - 1
apps/app/src/components/PageView/PageAlerts/TrashPageAlert/TrashPageAlert.tsx

@@ -65,7 +65,7 @@ const TrashPageAlertSubstance = (props: SubstanceProps): JSX.Element => {
         unlink(currentPagePath);
         unlink(currentPagePath);
 
 
         router.push(`/${pageId}`);
         router.push(`/${pageId}`);
-        fetchCurrentPage();
+        fetchCurrentPage({ force: true });
         mutateRecentlyUpdated();
         mutateRecentlyUpdated();
       } catch (err) {
       } catch (err) {
         // biome-ignore lint/style/noRestrictedImports: no-problem dynamic import
         // biome-ignore lint/style/noRestrictedImports: no-problem dynamic import

+ 1 - 1
apps/app/src/components/PageView/PageAlerts/WipPageAlert.tsx

@@ -21,7 +21,7 @@ export const WipPageAlert = (): JSX.Element => {
         .publish;
         .publish;
       await publish(pageId);
       await publish(pageId);
 
 
-      await fetchCurrentPage();
+      await fetchCurrentPage({ force: true });
 
 
       const mutatePageTree = (await import('~/stores/page-listing'))
       const mutatePageTree = (await import('~/stores/page-listing'))
         .mutatePageTree;
         .mutatePageTree;

+ 118 - 0
apps/app/src/states/page/use-fetch-current-page.spec.tsx

@@ -202,6 +202,124 @@ describe('useFetchCurrentPage - Integration Test', () => {
     expect(mockedApiv3Get).not.toHaveBeenCalled();
     expect(mockedApiv3Get).not.toHaveBeenCalled();
   });
   });
 
 
+  it('should force re-fetch even if target path is the same when force: true is set', async () => {
+    // Arrange: Current state is set
+    const currentPageData = createPageDataMock(
+      'page1',
+      '/same/path',
+      'old content',
+    );
+    store.set(currentPageIdAtom, currentPageData._id);
+    store.set(currentPageDataAtom, currentPageData);
+
+    // Arrange: API returns updated data
+    const updatedPageData = createPageDataMock(
+      'page1',
+      '/same/path',
+      'updated content',
+    );
+    mockedApiv3Get.mockResolvedValue(mockApiResponse(updatedPageData));
+
+    // Act
+    const { result } = renderHookWithProvider();
+    await result.current.fetchCurrentPage({ path: '/same/path', force: true });
+
+    // Assert
+    await waitFor(() => {
+      // 1. API should be called despite same path
+      expect(mockedApiv3Get).toHaveBeenCalledWith(
+        '/page',
+        expect.objectContaining({ path: '/same/path' }),
+      );
+
+      // 2. Atoms should be updated with new data
+      const pageData = store.get(currentPageDataAtom);
+      expect(pageData).toEqual(updatedPageData);
+      expect(pageData?.revision?.body).toBe('updated content');
+      expect(store.get(pageLoadingAtom)).toBe(false);
+    });
+  });
+
+  it('should force re-fetch even if target pageId is the same when force: true is set', async () => {
+    // Arrange: Current state is set with pageId
+    const currentPageData = createPageDataMock(
+      'pageId123',
+      '/some/path',
+      'old content',
+    );
+    store.set(currentPageIdAtom, currentPageData._id);
+    store.set(currentPageDataAtom, currentPageData);
+
+    // Arrange: API returns updated data
+    const updatedPageData = createPageDataMock(
+      'pageId123',
+      '/some/path',
+      'refreshed content',
+    );
+    mockedApiv3Get.mockResolvedValue(mockApiResponse(updatedPageData));
+
+    // Act
+    const { result } = renderHookWithProvider();
+    await result.current.fetchCurrentPage({ pageId: 'pageId123', force: true });
+
+    // Assert
+    await waitFor(() => {
+      // 1. API should be called despite same pageId
+      expect(mockedApiv3Get).toHaveBeenCalledWith(
+        '/page',
+        expect.objectContaining({ pageId: 'pageId123' }),
+      );
+
+      // 2. Atoms should be updated with new data
+      const pageData = store.get(currentPageDataAtom);
+      expect(pageData).toEqual(updatedPageData);
+      expect(pageData?.revision?.body).toBe('refreshed content');
+      expect(store.get(pageLoadingAtom)).toBe(false);
+    });
+  });
+
+  it('should force re-fetch for permalink path when force: true is set', async () => {
+    // Arrange: Current state with permalink
+    const permalinkId = '58a4569921a8424d00a1aa0e';
+    const currentPageData = createPageDataMock(
+      permalinkId,
+      '/actual/path',
+      'old content',
+    );
+    store.set(currentPageIdAtom, permalinkId);
+    store.set(currentPageDataAtom, currentPageData);
+
+    // Arrange: API returns updated data
+    const updatedPageData = createPageDataMock(
+      permalinkId,
+      '/actual/path',
+      'force refreshed content',
+    );
+    mockedApiv3Get.mockResolvedValue(mockApiResponse(updatedPageData));
+
+    // Act
+    const { result } = renderHookWithProvider();
+    await result.current.fetchCurrentPage({
+      path: `/${permalinkId}`,
+      force: true,
+    });
+
+    // Assert
+    await waitFor(() => {
+      // 1. API should be called despite cached permalink
+      expect(mockedApiv3Get).toHaveBeenCalledWith(
+        '/page',
+        expect.objectContaining({ pageId: permalinkId }),
+      );
+
+      // 2. Atoms should be updated with new data
+      const pageData = store.get(currentPageDataAtom);
+      expect(pageData).toEqual(updatedPageData);
+      expect(pageData?.revision?.body).toBe('force refreshed content');
+      expect(store.get(pageLoadingAtom)).toBe(false);
+    });
+  });
+
   it('should handle fetching the root page', async () => {
   it('should handle fetching the root page', async () => {
     // Arrange: Start on a regular page
     // Arrange: Start on a regular page
     const regularPageData = createPageDataMock(
     const regularPageData = createPageDataMock(

+ 6 - 0
apps/app/src/states/page/use-fetch-current-page.ts

@@ -31,6 +31,7 @@ type FetchPageArgs = {
   path?: string;
   path?: string;
   pageId?: string;
   pageId?: string;
   revisionId?: string;
   revisionId?: string;
+  force?: true;
 };
 };
 
 
 /**
 /**
@@ -66,6 +67,10 @@ const shouldUseCachedData = (
   currentPageData: IPagePopulatedToShowRevision | undefined,
   currentPageData: IPagePopulatedToShowRevision | undefined,
   decodedPathname?: string,
   decodedPathname?: string,
 ): boolean => {
 ): boolean => {
+  if (args?.force === true) {
+    return false;
+  }
+
   // Guard clause to prevent unnecessary fetching by pageId
   // Guard clause to prevent unnecessary fetching by pageId
   if (args?.pageId != null && args.pageId === currentPageId) {
   if (args?.pageId != null && args.pageId === currentPageId) {
     return true;
     return true;
@@ -220,6 +225,7 @@ export const useFetchCurrentPage = (): {
           set(pageNotFoundAtom, false);
           set(pageNotFoundAtom, false);
           set(isForbiddenAtom, false);
           set(isForbiddenAtom, false);
 
 
+          console.log({ newData });
           return newData;
           return newData;
         } catch (err) {
         } catch (err) {
           if (!Array.isArray(err) || err.length === 0) {
           if (!Array.isArray(err) || err.length === 0) {