Skip to content

Commit

Permalink
fix: fix route resolving error with hash and queries (close #1561) (#…
Browse files Browse the repository at this point in the history
…1562)

Co-authored-by: Mister-Hope <mister-hope@outlook.com>
Co-authored-by: meteorlxy <meteor.lxy@foxmail.com>
  • Loading branch information
3 people committed May 27, 2024
1 parent d3b3cc4 commit 4df59d4
Show file tree
Hide file tree
Showing 21 changed files with 359 additions and 102 deletions.
1 change: 1 addition & 0 deletions e2e/docs/.vuepress/theme/client/layouts/NotFound.vue
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<template>
<div class="e2e-theme-not-found">404 Not Found</div>
<div class="e2e-theme-not-found-content"><Content /></div>
</template>
2 changes: 2 additions & 0 deletions e2e/docs/404.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
routeMeta:
foo: bar
---

## NotFound H2
2 changes: 2 additions & 0 deletions e2e/docs/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
foo

## Home H2
28 changes: 28 additions & 0 deletions e2e/docs/router/navigate-by-link.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## Markdown Links

- [Home](/README.md)
- [404](/404.md)
- [Home with query](/README.md?home=true)
- [Home with query and hash](/README.md?home=true#home)
- [404 with hash](/404.md#404)
- [404 with hash and query](/404.md#404?notFound=true)

## HTML Links

<a :href="$withBase('/')" class="home">Home</a>
<a :href="$withBase('/404.html')" class="not-found">404</a>
<a :href="$withBase('/?home=true')" class="home-with-query">Home</a>
<a :href="$withBase('/?home=true#home')" class="home-with-query-and-hash">Home</a>
<a :href="$withBase('/404.html#404')" class="not-found-with-hash">404</a>
<a :href="$withBase('/404.html#404?notFound=true')" class="not-found-with-hash-and-query">404</a>

## Markdown Links with html paths

- [Home](/)
- [404](/404.html)
- [Home with query](/?home=true)
- [Home with query and hash](/?home=true#home)
- [404 with hash](/404.html#404)
- [404 with hash and query](/404.html#404?notFound=true)

> Non-recommended usage. HTML paths could not be prepended with `base` correctly.
37 changes: 37 additions & 0 deletions e2e/docs/router/navigate-by-router.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<button id="home" @click="goHome">Home</button>
<button id="not-found" @click="go404">404</button>

<button id="home-with-query" @click="goHomeWithQuery">Home</button>
<button id="home-with-query-and-hash" @click="goHomeWithQueryAndHash">Home</button>
<button id="not-found-with-hash" @click="go404WithHash">404</button>
<button id="not-found-with-hash-and-query" @click="go404WithHashAndQuery">404</button>

<script setup lang="ts">
import { useRouter } from 'vuepress/client';

const router = useRouter();

const goHome = () => {
router.push('/');
}

const go404 = () => {
router.push('/404.html');
}

const goHomeWithQuery = () => {
router.push('/?home=true');
}

const goHomeWithQueryAndHash = () => {
router.push('/?home=true#home');
}

const go404WithHash = () => {
router.push('/404.html#404');
}

const go404WithHashAndQuery = () => {
router.push('/404.html#404?notFound=true');
}
</script>
16 changes: 0 additions & 16 deletions e2e/docs/router/navigation.md

This file was deleted.

12 changes: 12 additions & 0 deletions e2e/docs/router/resolve-route-query-hash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Resolve Route FullPath

## Includes Query And Hash

- Search Query: {{ JSON.stringify(resolveRoute('/?query=1')) }}
- Hash: {{ JSON.stringify(resolveRoute('/#hash')) }}
- Search Query And Hash: {{ JSON.stringify(resolveRoute('/?query=1#hash')) }}
- Permalink And Search Query: {{ JSON.stringify(resolveRoute('/routes/permalinks/ascii-non-ascii.md?query=1')) }}

<script setup>
import { resolveRoute } from 'vuepress/client'
</script>
98 changes: 98 additions & 0 deletions e2e/tests/router/navigate-by-link.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { expect, test } from '@playwright/test'
import { BASE } from '../../utils/env'

test.beforeEach(async ({ page }) => {
await page.goto('router/navigate-by-link.html')
})

test.describe('markdown links', () => {
test('should navigate to home correctly', async ({ page }) => {
await page.locator('#markdown-links + ul > li > a').nth(0).click()
await expect(page).toHaveURL(`${BASE}`)
await expect(page.locator('#home-h2')).toHaveText('Home H2')
})

test('should navigate to 404 page correctly', async ({ page }) => {
await page.locator('#markdown-links + ul > li > a').nth(1).click()
await expect(page).toHaveURL(`${BASE}404.html`)
await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2')
})

test('should preserve query', async ({ page }) => {
await page.locator('#markdown-links + ul > li > a').nth(2).click()
await expect(page).toHaveURL(`${BASE}?home=true`)
await expect(page.locator('#home-h2')).toHaveText('Home H2')
})

test('should preserve query and hash', async ({ page }) => {
await page.locator('#markdown-links + ul > li > a').nth(3).click()
await expect(page).toHaveURL(`${BASE}?home=true#home`)
await expect(page.locator('#home-h2')).toHaveText('Home H2')
})

test('should preserve hash', async ({ page }) => {
await page.locator('#markdown-links + ul > li > a').nth(4).click()
await expect(page).toHaveURL(`${BASE}404.html#404`)
await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2')
})

test('should preserve hash and query', async ({ page }) => {
await page.locator('#markdown-links + ul > li > a').nth(5).click()
await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`)
await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2')
})
})

test.describe('html links', () => {
test('should navigate to home correctly', async ({ page }) => {
await page.locator('#html-links + p > a').nth(0).click()
await expect(page).toHaveURL(`${BASE}`)
await expect(page.locator('#home-h2')).toHaveText('Home H2')
})

test('should navigate to 404 page correctly', async ({ page }) => {
await page.locator('#html-links + p > a').nth(1).click()
await expect(page).toHaveURL(`${BASE}404.html`)
await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2')
})

test('should preserve query', async ({ page }) => {
await page.locator('#html-links + p > a').nth(2).click()
await expect(page).toHaveURL(`${BASE}?home=true`)
await expect(page.locator('#home-h2')).toHaveText('Home H2')
})

test('should preserve query and hash', async ({ page }) => {
await page.locator('#html-links + p > a').nth(3).click()
await expect(page).toHaveURL(`${BASE}?home=true#home`)
await expect(page.locator('#home-h2')).toHaveText('Home H2')
})

test('should preserve hash', async ({ page }) => {
await page.locator('#html-links + p > a').nth(4).click()
await expect(page).toHaveURL(`${BASE}404.html#404`)
await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2')
})

test('should preserve hash and query', async ({ page }) => {
await page.locator('#html-links + p > a').nth(5).click()
await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`)
await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2')
})
})

test.describe('markdown links with html paths', () => {
test('should navigate to home correctly', async ({ page }) => {
const locator = page
.locator('#markdown-links-with-html-paths + ul > li > a')
.nth(0)
if (BASE === '/') {
await locator.click()
await expect(page).toHaveURL('/')
await expect(page.locator('#home-h2')).toHaveText('Home H2')
} else {
await expect(locator).toHaveAttribute('href', '/')
await expect(locator).toHaveAttribute('target', '_blank')
}
})
})
42 changes: 42 additions & 0 deletions e2e/tests/router/navigate-by-router.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { expect, test } from '@playwright/test'
import { BASE } from '../../utils/env'

test.beforeEach(async ({ page }) => {
await page.goto('router/navigate-by-router.html')
})

test('should navigate to home correctly', async ({ page }) => {
await page.locator('#home').click()
await expect(page).toHaveURL(`${BASE}`)
await expect(page.locator('#home-h2')).toHaveText('Home H2')
})

test('should navigate to 404 page correctly', async ({ page }) => {
await page.locator('#not-found').click()
await expect(page).toHaveURL(`${BASE}404.html`)
await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2')
})

test('should preserve query', async ({ page }) => {
await page.locator('#home-with-query').click()
await expect(page).toHaveURL(`${BASE}?home=true`)
await expect(page.locator('#home-h2')).toHaveText('Home H2')
})

test('should preserve query and hash', async ({ page }) => {
await page.locator('#home-with-query-and-hash').click()
await expect(page).toHaveURL(`${BASE}?home=true#home`)
await expect(page.locator('#home-h2')).toHaveText('Home H2')
})

test('should preserve hash', async ({ page }) => {
await page.locator('#not-found-with-hash').click()
await expect(page).toHaveURL(`${BASE}404.html#404`)
await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2')
})

test('should preserve hash and query', async ({ page }) => {
await page.locator('#not-found-with-hash-and-query').click()
await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`)
await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2')
})
18 changes: 0 additions & 18 deletions e2e/tests/router/navigation.spec.ts

This file was deleted.

35 changes: 35 additions & 0 deletions e2e/tests/router/resolve-route-query-hash.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { expect, test } from '@playwright/test'

const testCases = [
{
path: '/?query=1',
notFound: false,
},
{
path: '/#hash',
notFound: false,
},
{
path: '/?query=1#hash',
notFound: false,
},
{
path: encodeURI('/永久链接-ascii-中文/?query=1'),
notFound: false,
},
]

test('should resolve routes when including both the query and hash', async ({
page,
}) => {
const listItemsLocator = await page
.locator('.e2e-theme-content #includes-query-and-hash + ul > li')
.all()

for (const [index, li] of listItemsLocator.entries()) {
const textContent = await li.textContent()
const resolvedRoute = JSON.parse(/: (\{.*\})\s*$/.exec(textContent!)![1])
expect(resolvedRoute.path).toEqual(testCases[index].path)
expect(resolvedRoute.notFound).toEqual(testCases[index].notFound)
}
})
4 changes: 2 additions & 2 deletions packages/client/src/components/RouteLink.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { computed, defineComponent, h } from 'vue'
import type { SlotsType, VNode } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { resolveRoutePath } from '../router/index.js'
import { resolveRouteFullPath } from '../router/index.js'

/**
* Forked from https://github.com/vuejs/router/blob/941b2131e80550009e5221d4db9f366b1fea3fd5/packages/router/src/RouterLink.ts#L293
Expand Down Expand Up @@ -91,7 +91,7 @@ export const RouteLink = defineComponent({
const path = computed(() =>
props.to.startsWith('#') || props.to.startsWith('?')
? props.to
: `${__VUEPRESS_BASE__}${resolveRoutePath(props.to, route.path).substring(1)}`,
: `${__VUEPRESS_BASE__}${resolveRouteFullPath(props.to, route.path).substring(1)}`,
)

return () =>
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export type { Router, RouteLocationNormalizedLoaded } from 'vue-router'
export { useRoute, useRouter } from 'vue-router'

export * from './resolveRoute.js'
export * from './resolveRouteFullPath.js'
export * from './resolveRoutePath.js'
23 changes: 17 additions & 6 deletions packages/client/src/router/resolveRoute.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { splitPath } from '@vuepress/shared'
import { routes } from '../internal/routes.js'
import type { Route, RouteMeta } from '../types/index.js'
import { resolveRoutePath } from './resolveRoutePath.js'
Expand All @@ -15,15 +16,25 @@ export const resolveRoute = <T extends RouteMeta = RouteMeta>(
path: string,
currentPath?: string,
): ResolvedRoute<T> => {
const routePath = resolveRoutePath(path, currentPath)
const route = routes.value[routePath] ?? {
...routes.value['/404.html'],
notFound: true,
// get only the pathname from the path
const { pathname, hashAndQueries } = splitPath(path)

// resolve the route path
const routePath = resolveRoutePath(pathname, currentPath)
const routeFullPath = routePath + hashAndQueries

// the route not found
if (!routes.value[routePath]) {
return {
...routes.value['/404.html'],
path: routeFullPath,
notFound: true,
} as ResolvedRoute<T>
}

return {
path: routePath,
...routes.value[routePath],
path: routeFullPath,
notFound: false,
...route,
} as ResolvedRoute<T>
}
13 changes: 13 additions & 0 deletions packages/client/src/router/resolveRouteFullPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { splitPath } from '@vuepress/shared'
import { resolveRoutePath } from './resolveRoutePath.js'

/**
* Resolve route full path with given raw path
*/
export const resolveRouteFullPath = (
path: string,
currentPath?: string,
): string => {
const { pathname, hashAndQueries } = splitPath(path)
return resolveRoutePath(pathname, currentPath) + hashAndQueries
}
Loading

0 comments on commit 4df59d4

Please sign in to comment.