scottrippey / next-router-mock

Mock implementation of the Next.js Router

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Feature request: support NextJS rewrites

karl-run opened this issue · comments

I might have programmed my self a bit into a corner here, but I have an app where I have the following rewrites:

async rewrites() {
    return [
        {
            source: '/',
            destination: '/null',
        },
        {
            source: '/some-route/:someId',
            destination: '/:someId',
        },
    ]
}

I do this in order to have a single page, called [someId].tsx to handle both root (e.g. no ID passed), and /some-route/some-value. This is of course a bit weird, but that's not important here. 😄 But to handle both rewrites pointing to the same page, I expect, according to the rewrites, the path parameter to be an actual string of type 'null' or an actual ID, and handle this is the code accordingly.

So naturally when testing this using jest and next-router-mock, when the application code redirects to / it would normally hit the rewrite, but in the test it doesn't. So the [someId].tsx page is rendered with an "illegal" state (illegal according to my own rewrite rules).

I could implement support for this test-only case in the application code, but I'm trying to avoid that. I also looked at using mockRouter.events.on('routeChangeStart', (evts) => {...}) as a way to handle my own rewrites, but it seems that was a dead end.

I know this is a bit of a weird case, but if anyone has any smart suggestions I'd be grateful. 😄 It would of course be cool if the dynamic routing could be configured to handle these kind of rewrites as well, but I'm not sure how that would work.

I'm not familiar with Next's rewrites feature, so I haven't considered how to handle this situation. So I'll just share some initial thoughts.

This library has an internally-used property on the router object, called router.pathParser:
https://github.com/scottrippey/next-router-mock/blob/main/src/MemoryRouter.tsx#L76

  pathParser: ((urlObject: UrlObjectComplete) => UrlObjectComplete) | undefined = undefined;

This is used for the "dynamic routes" feature ... but I'm thinking maybe you could use it for this custom rewrite logic?

An example:

import router from 'next-router-mock';
router.pathParser = (url) => {
  if (url.pathname === '/') {
    url.query.someId = 'null';
  }
  if (url.pathname === '/some-route/:someId') {
    url.pathname = '/:someId';
  }
  return url;
};

However, there are some difficulties with this approach ...

  1. If you're already using dynamic routes, then pathParser will already be used for parsing the dynamic routes, so you can't just override it. You'd have to do something like:
const dynamicRouteParser = router.pathParser;
router.pathParser = (url) => {
  url = dynamicRouteParser(url);
  ...
  1. This pathParser isn't really documented, and I consider it an "internal" ... so if we wanted to add better support for rewrites, it might get refactored ... in which case, your custom implementation would be broken.

I would be eager/happy to have better support for rewrites, similar to how we handle dynamic routes. It would be cool to do something like:

mockRouter.registerRewrites([ { source, destination }, ... ]);

router.pathParser was kind of exactly what I was looking for. I was scrolling through the properties yesterday looking for something like that but I must have glanced over it. 🤦

Your approach of saving the original reference and creating a new pathParser completely solved my problem. Thanks for the quick and in depth response. 😄

mockRouter.registerRewrites would indeed be a cool feature.

After looking at the rewrite docs it's clearly not a simple implementation ... so it would probably only be possible to mock if we could import their logic (we do this for the registerPaths feature).
For now, I'm considering improving the pathParser approach to better support multiple parsers, which would make it easier to lightly mock rewrites.

Please see PR #50 to see how I'd like to fix this

@karl-run So with the above PR, this is how you'd handle mocking rewrite logic:

mockRouter.useParser((url) => {
  if (url.pathname === '/') {
    url.query.someId = 'null';
  }
  if (url.pathname === '/some-route/:someId') {
    url.pathname = '/:someId';
  }
});

What do you think?

That looks pretty good! It would solve my problems.

Published as 0.7.0