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

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

@@ -0,0 +1,120 @@
+import type { IPagePopulatedToShowRevision } from '@growi/core';
+import { renderHook, waitFor } from '@testing-library/react';
+import type { NextRouter } from 'next/router';
+import { useRouter } from 'next/router';
+import { vi } from 'vitest';
+import { mock } from 'vitest-mock-extended';
+
+import { useFetchCurrentPage } from '~/states/page';
+import { useEditingMarkdown } from '~/stores/editor';
+
+import { useSameRouteNavigation } from './use-same-route-navigation';
+
+// Mock dependencies
+vi.mock('next/router', () => ({
+  useRouter: vi.fn(),
+}));
+vi.mock('~/states/page');
+vi.mock('~/stores/editor');
+
+// Define stable mock functions outside of describe/beforeEach
+const mockFetchCurrentPage = vi.fn();
+const mockMutateEditingMarkdown = vi.fn();
+
+const pageDataMock = mock<IPagePopulatedToShowRevision>({
+  revision: {
+    body: 'Test page content',
+  },
+});
+
+describe('useSameRouteNavigation', () => {
+  // Define a mutable router object that can be accessed and modified in tests
+  let mockRouter: { asPath: string };
+
+  beforeEach(() => {
+    // Clear mocks and reset implementations before each test
+    vi.clearAllMocks();
+
+    // Initialize the mutable router object
+    mockRouter = {
+      asPath: '',
+    };
+
+    // Mock useRouter to return our mutable router object
+    (useRouter as ReturnType<typeof vi.fn>).mockReturnValue(mockRouter as NextRouter);
+
+    (useFetchCurrentPage as ReturnType<typeof vi.fn>).mockReturnValue({
+      fetchCurrentPage: mockFetchCurrentPage,
+    });
+
+    (useEditingMarkdown as ReturnType<typeof vi.fn>).mockReturnValue({
+      mutate: mockMutateEditingMarkdown,
+    });
+
+    mockFetchCurrentPage.mockResolvedValue(pageDataMock);
+  });
+
+  it('should call fetchCurrentPage and mutateEditingMarkdown on path change', async() => {
+    // Arrange
+    mockRouter.asPath = '/initial-path';
+    const { rerender } = renderHook(() => useSameRouteNavigation());
+
+    // Act: Simulate navigation
+    mockRouter.asPath = '/new-path';
+    rerender();
+
+    // Assert
+    await waitFor(() => {
+      // 1. fetchCurrentPage is called with the new path
+      expect(mockFetchCurrentPage).toHaveBeenCalledWith({ path: '/new-path' });
+
+      // 2. mutateEditingMarkdown is called with the content from the fetched page
+      expect(mockMutateEditingMarkdown).toHaveBeenCalledWith(pageDataMock.revision?.body);
+    });
+  });
+
+  it('should not trigger effects if the path does not change', async() => {
+    // Arrange
+    mockRouter.asPath = '/same-path';
+    const { rerender } = renderHook(() => useSameRouteNavigation());
+    // call on initial render
+    await waitFor(() => {
+      expect(mockFetchCurrentPage).toHaveBeenCalledTimes(1);
+      expect(mockMutateEditingMarkdown).toHaveBeenCalledTimes(1);
+    });
+
+    // Act: Rerender with the same path
+    rerender();
+
+    // Assert
+    // A short delay to ensure no async operations are triggered
+    await new Promise(resolve => setTimeout(resolve, 100));
+    expect(mockFetchCurrentPage).toHaveBeenCalledTimes(1); // Should not be called again
+    expect(mockMutateEditingMarkdown).toHaveBeenCalledTimes(1);
+  });
+
+  it('should not call mutateEditingMarkdown if pageData or revision is null', async() => {
+    // Arrange: first, fetch successfully
+    mockRouter.asPath = '/initial-path';
+    const { rerender } = renderHook(() => useSameRouteNavigation());
+    await waitFor(() => {
+      expect(mockFetchCurrentPage).toHaveBeenCalledTimes(1);
+      expect(mockMutateEditingMarkdown).toHaveBeenCalledTimes(1);
+    });
+
+    // Arrange: next, fetch fails (returns null)
+    mockFetchCurrentPage.mockResolvedValue(null);
+
+    // Act
+    mockRouter.asPath = '/path-with-no-data';
+    rerender();
+
+    // Assert
+    await waitFor(() => {
+      // fetch should be called again
+      expect(mockFetchCurrentPage).toHaveBeenCalledWith({ path: '/path-with-no-data' });
+      // but mutate should not be called again
+      expect(mockMutateEditingMarkdown).toHaveBeenCalledTimes(1);
+    });
+  });
+});