scottrippey / next-router-mock

Mock implementation of the Next.js Router
MIT License
393 stars 38 forks source link

MemoryRouterProvider url property does not work properly #108

Open SalahAdDin opened 9 months ago

SalahAdDin commented 9 months ago

I'm trying to text links with this package and I am getting inconsistent behavior.

I defined a custom render as follows:

  const mockPath = "/streaming";

  const mockItem = {
    label: "Live",
    path: mockPath,
    //decorator: "streaming-status",
  };

const renderWithRouter = (ui: React.ReactElement, { route = "/" } = {}) => {
  const wrapper = ({ children }: { children: React.ReactNode }) => (
    <MemoryRouterProvider url={route}>{children}</MemoryRouterProvider>
  );
  return {
    user: userEvent.setup(),
    ...render(ui, { wrapper }),
  };
};

And I'm using it on my test like this:

  const setup = () => {
    const view = renderWithRouter(
      <Navbar>
        <HeaderItemBase {...mockItem} />
      </Navbar>,
      { route: mockItem.path }
    );

    return view;
  };

  it("should have blue text color when route is same with path", async () => {
    const view = setup();
    expect(view).toBeTruthy();

    const { user } = view;

    const link = screen.getByRole("link", { name: mockItem.label });

    await user.click(link);

    console.log("Current Path", mockRouter.asPath);
    expect(mockRouter.asPath).toStrictEqual(mockPath);

    /** TODO
     * `next/navigation` is not fully suported on the mocking library yet.
     * https://github.com/scottrippey/next-router-mock/issues/67
     * expect(link).toHaveClass("text-primary-400");
     *  */
  });

Logging the router path we get:

17:32:15 | header item base > should have white text color when route is different to path | stdout

This is the route /streaming
Current Path /

17:32:15 | header item base > should have blue text color when route is same with path | stdout

This is the route /streaming
Current Path /

17:32:16 | header item base > should have blue text color when route is same with path | stdout

Current Path after Click /

Yeah, it should be "Current Path /streaming".

Also, after clicking on the button, it should navigate easily, but it seems the route is always overwritten to be the given one and it does not change.

When using it without url property it works well, and navigates correctly.

It seems I got the property wrong, it should work as an initial route, right? Could you write an example of writing it as a provider with options using testing library?

kotarella1110 commented 9 months ago

I've encountered the same issue before as well. Back then, I investigated the code, and it appears that this is probably not a bug but likely by design. The reason for this specification is unclear, but as you can see in the following code, when you pass the url prop to MemoryRouterProvider, it's implemented to replace the singleton router with an isolate router.

https://github.com/scottrippey/next-router-mock/blob/3abed1b71b6f1c7e97477fa777b0a01fc94c7325/src/MemoryRouterProvider/MemoryRouterProvider.tsx#L26-L29

To resolve this issue, you can create a wrapper for MemoryRouterProvider. Set the url passed in via props using setCurrentUrl as shown below and pass the remaining props other than url to MemoryRouterProvider:

import singletonRouter from 'next-router-mock';
import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider';
import type { MemoryRouterProviderProps } from 'next-router-mock/dist/MemoryRouterProvider/MemoryRouterProvider';

const SingletonRouterProvider = ({ url, ...rest }: MemoryRouterProviderProps) => {
  useEffect(() => {
    if (!url) {
      return;
    }
    singletonRouter.setCurrentUrl(url);
  }, [url]);

  return <MemoryRouterProvider {...rest} />;
};

const renderWithRouter = (ui: React.ReactElement, { route = "/" } = {}) => {
  const wrapper = ({ children }: { children: React.ReactNode }) => (
    <SingletonRouterProvider url={route}>{children}</SingletonRouterProvider>
  );
  return {
    user: userEvent.setup(),
    ...render(ui, { wrapper }),
  };
};
kotarella1110 commented 9 months ago

@scottrippey

The reason for this specification is unclear, but as you can see in the following code, when you pass the url prop to MemoryRouterProvider, it's implemented to replace the singleton router with an isolate router.

It appears that this implementation was added in the following PR, but could you please explain the reason behind it?

https://github.com/scottrippey/next-router-mock/pull/66

SalahAdDin commented 3 weeks ago

@scottrippey Are you here?