diff --git a/src/data-loaders/defineColadaLoader.ts b/src/data-loaders/defineColadaLoader.ts index ac047b44a..9ae4f04e9 100644 --- a/src/data-loaders/defineColadaLoader.ts +++ b/src/data-loaders/defineColadaLoader.ts @@ -363,6 +363,8 @@ export function defineColadaLoader( !entry || // we are nested and the parent is loading a different route than us (parentEntry && entry.pendingTo !== route) + // The user somehow rendered the page without a navigation + || !entry.pendingLoad ) { // console.log( // `🔁 loading from useData for "${options.key}": "${route.fullPath}"` diff --git a/src/data-loaders/defineLoader.ts b/src/data-loaders/defineLoader.ts index fef640c42..d18a5f296 100644 --- a/src/data-loaders/defineLoader.ts +++ b/src/data-loaders/defineLoader.ts @@ -302,6 +302,9 @@ export function defineBasicLoader( (parentEntry && entry.pendingTo !== route) // we could also check for: but that would break nested loaders since they need to be always called to be associated with the parent // && entry.to !== route + // the user managed to render the router view after a valid navigation + a failed navigation + // https://github.com/posva/unplugin-vue-router/issues/495 + || !entry.pendingLoad ) { // console.log( // `🔁 loading from useData for "${options.key}": "${route.fullPath}"` diff --git a/tests/data-loaders/tester.ts b/tests/data-loaders/tester.ts index 9219cc26d..976a4c178 100644 --- a/tests/data-loaders/tester.ts +++ b/tests/data-loaders/tester.ts @@ -1,7 +1,15 @@ /** * @vitest-environment happy-dom */ -import { type App, defineComponent, inject, type Plugin } from 'vue' +import { + type App, + defineComponent, + h, + inject, + nextTick, + type Plugin, + ref, +} from 'vue' import { beforeEach, describe, expect, it, vi } from 'vitest' import { flushPromises, mount } from '@vue/test-utils' import { getRouter } from 'vue-router-mock' @@ -1109,6 +1117,58 @@ export function testDefineLoader( it.todo('can be first non-lazy then lazy', async () => {}) it.todo('can be first non-lazy then lazy', async () => {}) + // https://github.com/posva/unplugin-vue-router/issues/495 + // in the issue above we have one page with a loader + // this page is conditionally rendered based on an error state + // when resetting the error state, there is also a duplicated navigation + // that invalidates any pendingLoad and renders the page again + // since there is no navigation, loaders are not called again and + // there is no pendingLoad + it('gracefully handles a loader without a pendingLoad', async () => { + const l1 = mockedLoader({ lazy: false, key: 'l1' }) + const router = getRouter() + router.addRoute({ + name: 'a', + path: '/a', + component: defineComponent({ + setup() { + const { data } = l1.loader() + return { data } + }, + template: `

{{ data }}

`, + }), + meta: { + loaders: [l1.loader], + }, + }) + l1.spy.mockResolvedValue('ok') + + const isVisible = ref(true) + + + const wrapper = mount( + () => (isVisible.value ? h(RouterViewMock) : h('p', ['hidden'])), + { + global: { + plugins: [ + [DataLoaderPlugin, { router }], + ...(plugins?.(customContext!) || []), + ], + }, + } + ) + + await router.push('/a') + expect(wrapper.text()).toBe('ok') + isVisible.value = false + await nextTick() + expect(wrapper.text()).toBe('hidden') + await router.push('/a') // failed duplicated navigation + isVisible.value = true + await nextTick() + expect(wrapper.text()).toBe('ok') + }) + describe('app.runWithContext()', () => { it('can inject globals', async () => { const { router, useData, app } = singleLoaderOneRoute(