Skip to content

Setting initial route fails when routes have dynamic import #130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ChristophWalter opened this issue Mar 30, 2020 · 13 comments
Closed

Setting initial route fails when routes have dynamic import #130

ChristophWalter opened this issue Mar 30, 2020 · 13 comments
Labels
bug Something isn't working

Comments

@ChristophWalter
Copy link

Describe the bug
There is a test which shows that initialising a router can be done by pushing a path. We use dynamic imports to lazy load our routes. In combination, this results that the initial route is not set at all.

const {queryByTestId} = render(App, {routes}, (vue, store, router) => {
router.push('/about')
})

To Reproduce
Change the following line to

{path: '/about', component: () => import('./components/Router/About.vue')},

and run the tests.

{path: '/about', component: About},

Expected behavior
I expected that to work right out of the box or with help of waitFor or something... But I did not manage to get it working.

@ChristophWalter ChristophWalter added the bug Something isn't working label Mar 30, 2020
@ChristophWalter
Copy link
Author

Further investigations showed that the error only occurs when using async import and nested routes in combination. I reproduced it in this fork commit: ChristophWalter@57d060f

@ChristophWalter
Copy link
Author

This is really hard to debug, but I discovered something interesting:

  • The router.push() command fails. This can be seen using the two callback parameter.
  • It seems to be related to the execution order. Mounting the component first and pushing the new routes afterwards works. But I would like to avoid using a reference to the router:
    let routerReference
    const {queryByTestId} = render(
      ...,
      (vue, store, router) => {
        routerReference = router
      },
    )
    routerReference.push('/about/me')
    await waitFor(() =>
      expect(queryByTestId('location-display')).toHaveTextContent('/about/me'),
    )

Is it somehow possible to wait for the configurationCallback to finish before mounting the component? Then we could use await router.push()

@ChristophWalter
Copy link
Author

I created a pull request to fix this. But the current solution changes the public interface from

render(App)

to

await render(App)

Any ideas on how to avoid this?

@afontcu
Copy link
Member

afontcu commented Apr 1, 2020

Hi! Thanks for the extensive reporting and for working on this 🤗

I feel too that changing the public render interface to a Promise is a big deal. It would solve, however, several issues I encountered regarding the first "tick", where stuff in mounted() or created() is not executed until we await one clock tick.

It is a big deal, but I wonder if there's any workaround on this. I'll give a look soon and see what I can come up with!

Have a nice day 😄

@ChristophWalter
Copy link
Author

Thanks, you too! 🤗

Of course, we could also call router.push() after mounting within vue-testing-library.js or call another callback there.

But that would still be asynchronous with potential side effects. And the mounted app would not start with the initial route, rather navigating there right after mount. (Which actually is currently happening even if there are no async imports.)

Another (solution?) would be to accept the router getting passed to mount and not the array of routes. That would be more boilerplate for every test, but than everyone could configure the router upfront as needed.

@ChristophWalter
Copy link
Author

By the way: Do you know of any way to improve debugging output in jest? The router.push() here throws an error:

test('setting initial route for nested routes with async component import', () => {
  const {queryByTestId} = render(
    App, {}, (vue, store, router) => {
      router.push('/about/me') // This line throws an error
    },
  )
})

But the jest output looks like the following:

  thrown: undefined

      35 | })
      36 | 
    > 37 | test('setting initial route for nested routes with async component import', async () => {
         | ^
      38 |   const {queryByTestId} =  render(
      39 |     App,
      40 |     {

      at new Spec (node_modules/jest-jasmine2/build/jasmine/Spec.js:116:22)
      at Object.<anonymous> (src/__tests__/vue-router.js:37:1)

It would be very helpful to get a line number pointing to the real error...

@dfcook
Copy link
Collaborator

dfcook commented Apr 2, 2020

Have you tried adding verbose: true to your jest config?

@ChristophWalter
Copy link
Author

Have you tried adding verbose: true to your jest config?

I just tried it, but I can see no difference...

@afontcu
Copy link
Member

afontcu commented Apr 5, 2020

By the way: Do you know of any way to improve debugging output in jest? The router.push() here throws an error:

test('setting initial route for nested routes with async component import', () => {
  const {queryByTestId} = render(
    App, {}, (vue, store, router) => {
      router.push('/about/me') // This line throws an error
    },
  )
})

I guess this throws because there's no initial route, so router is null :) we're only initializing the Router if some route is present: https://github.com/testing-library/vue-testing-library/blob/master/src/vue-testing-library.js#L38-L45

@ChristophWalter
Copy link
Author

Hi @afontcu, I was referring to the lines of this commit. I just wanted to show a minimal example, sorry for the confusion. The routes are defined and the router.push('/about/me') throws an error when not waiting for its promise to resolve before continuing with mounting the app. It was however hard to debug this as jest just said: thrown: undefined somewhere in this test.

I think the most straight forward way to fix this is accepting a router object to be passed to the render function besides the routes. So everyone can work with the router before mounting as needed. I updated the PR according to this: #132

@cilice
Copy link
Contributor

cilice commented May 25, 2020

@ChristophWalter You can always do that in userland albeit it's cumbersome, all VTL is doing is to spread the options on the config thing. so you might as well do:

const router = new VueRouter({
  routes: [
    {
      path: "/about",
      component: () => import("./components/Router/AboutWrapper.vue"),
      children: [
        {
          path: "me",
          component: () => import("./components/Router/About.vue")
        }
      ]
    }
  ]
});
await router.push("/about/me");

const { queryByTestId } = render(App, {}, vue => {
  vue.use(VueRouter);
  const config = {
    router
  };
});

@afontcu
Copy link
Member

afontcu commented Jun 16, 2020

I feel the suggestion above is valid enough so that we don't need to overcomplicate router setup. As mentioned, VTL provides a comfortable way to set up the store and router, but you can always get the Vue instance and use it in your favor.

Thanks for posting this, though! 👍 It'll be really helpful for anyone else running into issues with dynamic imports.

Closing it up!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

5 participants
@cilice @afontcu @dfcook @ChristophWalter and others