From fdefcd8077f9343657f7ab6a23bd8613f1553803 Mon Sep 17 00:00:00 2001 From: Mister-Hope Date: Thu, 20 Feb 2025 19:42:05 +0800 Subject: [PATCH 1/5] feat: add route options --- e2e/docs/router/navigate-by-link.md | 24 +- e2e/docs/router/navigate-by-router.md | 70 +++- e2e/tests/router/navigate-by-link.spec.ts | 110 +++++- e2e/tests/router/navigate-by-router.spec.ts | 96 ++++-- e2e/tests/router/resolve-route.spec.ts | 2 +- .../src/plugins/vuepressConfigPlugin.ts | 1 + .../src/config/handlePluginDefine.ts | 1 + .../cli/src/commands/dev/watchPageFiles.ts | 2 +- packages/client/src/router/index.ts | 2 +- packages/client/src/router/resolveRoute.ts | 16 +- ...eRoutePath.ts => resolveRouteCleanPath.ts} | 2 +- .../client/src/router/resolveRouteFullPath.ts | 12 +- packages/client/types.d.ts | 1 + packages/core/src/app/resolveAppOptions.ts | 21 +- packages/core/src/app/resolveAppPages.ts | 6 +- packages/core/src/page/resolvePagePath.ts | 6 +- .../core/src/page/resolvePagePermalink.ts | 2 +- packages/core/src/types/app/options.ts | 54 ++- .../core/tests/app/resolveAppOptions.spec.ts | 7 +- .../core/tests/app/resolveAppPages.spec.ts | 12 +- .../core/tests/page/inferPagePath.spec.ts | 10 +- .../core/tests/page/resolvePagePath.spec.ts | 4 +- .../src/plugins/linksPlugin/linksPlugin.ts | 21 +- packages/shared/src/utils/routes/index.ts | 1 + .../shared/src/utils/routes/inferRoutePath.ts | 18 +- .../utils/routes/resolveRoutePathWithExt.ts | 2 + .../tests/routes/inferRoutePath.spec.ts | 42 +-- .../tests/routes/normalizeRoutePath.spec.ts | 324 +++++++++--------- 28 files changed, 567 insertions(+), 302 deletions(-) rename packages/client/src/router/{resolveRoutePath.ts => resolveRouteCleanPath.ts} (96%) create mode 100644 packages/shared/src/utils/routes/resolveRoutePathWithExt.ts diff --git a/e2e/docs/router/navigate-by-link.md b/e2e/docs/router/navigate-by-link.md index e496113275..5405b962e2 100644 --- a/e2e/docs/router/navigate-by-link.md +++ b/e2e/docs/router/navigate-by-link.md @@ -16,13 +16,33 @@ 404 404 +## HTML Clean Links + +Home +404 +Home +Home +404 +404 + +## Markdown Clean Links + +> Non-recommended usage. HTML paths could not be prepended with `base` correctly. + +- [Home](/) +- [404](/404) +- [Home with query](/?home=true) +- [Home with query and hash](/?home=true#home) +- [404 with hash](/404#404) +- [404 with hash and query](/404#404?notFound=true) + ## Markdown Links with html paths +> Non-recommended usage. HTML paths could not be prepended with `base` correctly. + - [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. diff --git a/e2e/docs/router/navigate-by-router.md b/e2e/docs/router/navigate-by-router.md index b28397dbf7..b96db71d21 100644 --- a/e2e/docs/router/navigate-by-router.md +++ b/e2e/docs/router/navigate-by-router.md @@ -1,37 +1,71 @@ - - +
+ + + + + + +
- - - - +
+ + + + + + +
diff --git a/e2e/tests/router/navigate-by-link.spec.ts b/e2e/tests/router/navigate-by-link.spec.ts index 7c6f974992..7ef3ae652b 100644 --- a/e2e/tests/router/navigate-by-link.spec.ts +++ b/e2e/tests/router/navigate-by-link.spec.ts @@ -6,76 +6,160 @@ test.beforeEach(async ({ page }) => { }) test.describe('markdown links', () => { + const selector = '#markdown-links + ul > li > a' + test('should navigate to home correctly', async ({ page }) => { - await page.locator('#markdown-links + ul > li > a').nth(0).click() + await page.locator(selector).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 page.locator(selector).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 page.locator(selector).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 page.locator(selector).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 page.locator(selector).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 page.locator(selector).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', () => { + const selector = '#html-links + p > a' + + test('should navigate to home correctly', async ({ page }) => { + await page.locator(selector).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(selector).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(selector).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(selector).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(selector).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(selector).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 clean links', () => { + const selector = '#html-clean-links + p > a' + + test('should navigate to home correctly', async ({ page }) => { + await page.locator(selector).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-clean-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(selector).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(selector).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(selector).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(selector).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 clean links', () => { + const selector = '#markdown-clean-links + blockquote + ul > li > a' + test('should navigate to home correctly', async ({ page }) => { - await page.locator('#html-links + p > a').nth(0).click() + await page.locator(selector).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 page.locator(selector).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 page.locator(selector).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 page.locator(selector).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 page.locator(selector).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 page.locator(selector).nth(5).click() await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`) await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') }) @@ -84,7 +168,7 @@ test.describe('html links', () => { 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') + .locator('#markdown-links-with-html-paths + blockquote + ul > li > a') .nth(0) if (BASE === '/') { await locator.click() diff --git a/e2e/tests/router/navigate-by-router.spec.ts b/e2e/tests/router/navigate-by-router.spec.ts index b847a839a2..cfe6b533a5 100644 --- a/e2e/tests/router/navigate-by-router.spec.ts +++ b/e2e/tests/router/navigate-by-router.spec.ts @@ -5,38 +5,86 @@ 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.describe('should navigate to home correctly', () => { + test('full', async ({ page }) => { + await page.locator('#full .home').click() + await expect(page).toHaveURL(BASE) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + }) + + test('clean', async ({ page }) => { + await page.locator('#clean .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.describe('should navigate to 404 page correctly', () => { + test('full', async ({ page }) => { + await page.locator('#full .not-found').click() + await expect(page).toHaveURL(`${BASE}404.html`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + }) + + test('clean', async ({ page }) => { + await page.locator('#clean .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.describe('should preserve query', () => { + test('full', async ({ page }) => { + await page.locator('#full .home-with-query').click() + await expect(page).toHaveURL(`${BASE}?home=true`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + }) + + test('clean', async ({ page }) => { + await page.locator('#clean .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.describe('should preserve query and hash', () => { + test('full', async ({ page }) => { + await page.locator('#full .home-with-query-and-hash').click() + await expect(page).toHaveURL(`${BASE}?home=true#home`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + }) + + test('clean', async ({ page }) => { + await page.locator('#clean .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.describe('should preserve hash', () => { + test('full', async ({ page }) => { + await page.locator('#full .not-found-with-hash').click() + await expect(page).toHaveURL(`${BASE}404.html#404`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + }) + + test('clean', async ({ page }) => { + await page.locator('#clean .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') +test.describe('should preserve hash and query', () => { + test('full', async ({ page }) => { + await page.locator('#full .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') + }) + + test('clean', async ({ page }) => { + await page.locator('#clean .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') + }) }) diff --git a/e2e/tests/router/resolve-route.spec.ts b/e2e/tests/router/resolve-route.spec.ts index bad4350843..d9b9b12898 100644 --- a/e2e/tests/router/resolve-route.spec.ts +++ b/e2e/tests/router/resolve-route.spec.ts @@ -45,7 +45,7 @@ const TEST_CASES = [ selector: '#route-meta', expected: { path: '/page-data/route-meta.html', - meta: { a: 0, b: 2, c: 3 }, + meta: { a: 0, c: 3 }, notFound: false, }, }, diff --git a/packages/bundler-vite/src/plugins/vuepressConfigPlugin.ts b/packages/bundler-vite/src/plugins/vuepressConfigPlugin.ts index b1187019f0..ba33da2c64 100644 --- a/packages/bundler-vite/src/plugins/vuepressConfigPlugin.ts +++ b/packages/bundler-vite/src/plugins/vuepressConfigPlugin.ts @@ -65,6 +65,7 @@ const resolveDefine = async ({ const define: UserConfig['define'] = { __VUEPRESS_VERSION__: JSON.stringify(app.version), __VUEPRESS_BASE__: JSON.stringify(app.options.base), + __VUEPRESS_CLEAN_URL__: JSON.stringify(app.options.route.cleanUrl), __VUEPRESS_DEV__: JSON.stringify(!isBuild), __VUEPRESS_SSR__: JSON.stringify(isServer), // @see http://link.vuejs.org/feature-flags diff --git a/packages/bundler-webpack/src/config/handlePluginDefine.ts b/packages/bundler-webpack/src/config/handlePluginDefine.ts index 8fdf06b1ac..ada678a3ab 100644 --- a/packages/bundler-webpack/src/config/handlePluginDefine.ts +++ b/packages/bundler-webpack/src/config/handlePluginDefine.ts @@ -21,6 +21,7 @@ export const handlePluginDefine = async ({ { __VUEPRESS_VERSION__: JSON.stringify(app.version), __VUEPRESS_BASE__: JSON.stringify(app.options.base), + __VUEPRESS_CLEAN_URL__: JSON.stringify(app.options.route.cleanUrl), __VUEPRESS_DEV__: JSON.stringify(!isBuild), __VUEPRESS_SSR__: JSON.stringify(isServer), // @see http://link.vuejs.org/feature-flags diff --git a/packages/cli/src/commands/dev/watchPageFiles.ts b/packages/cli/src/commands/dev/watchPageFiles.ts index d9b884a2ee..b89dff9847 100644 --- a/packages/cli/src/commands/dev/watchPageFiles.ts +++ b/packages/cli/src/commands/dev/watchPageFiles.ts @@ -43,7 +43,7 @@ export const watchPageFiles = (app: App): FSWatcher[] => { }) // watch page files - const pagesWatcher = chokidar.watch(app.options.pagePatterns, { + const pagesWatcher = chokidar.watch(app.options.route.pagePatterns, { cwd: app.dir.source(), ignoreInitial: true, }) diff --git a/packages/client/src/router/index.ts b/packages/client/src/router/index.ts index 206f6f2f2b..3fba49b350 100644 --- a/packages/client/src/router/index.ts +++ b/packages/client/src/router/index.ts @@ -3,4 +3,4 @@ export { useRoute, useRouter } from 'vue-router' export * from './resolveRoute.js' export * from './resolveRouteFullPath.js' -export * from './resolveRoutePath.js' +export * from './resolveRouteCleanPath.js' diff --git a/packages/client/src/router/resolveRoute.ts b/packages/client/src/router/resolveRoute.ts index 8f848a56a3..15e8acd07c 100644 --- a/packages/client/src/router/resolveRoute.ts +++ b/packages/client/src/router/resolveRoute.ts @@ -1,7 +1,7 @@ -import { splitPath } from '@vuepress/shared' +import { resolveRoutePathWithExt, splitPath } from '@vuepress/shared' import { routes } from '../internal/routes.js' import type { Route, RouteMeta } from '../types/index.js' -import { resolveRoutePath } from './resolveRoutePath.js' +import { resolveRouteCleanPath } from './resolveRouteCleanPath.js' export interface ResolvedRoute extends Route { @@ -20,21 +20,23 @@ export const resolveRoute = ( const { pathname, hashAndQueries } = splitPath(path) // resolve the route path - const routePath = resolveRoutePath(pathname, currentPath) - const routeFullPath = routePath + hashAndQueries + const cleanRoutePath = resolveRouteCleanPath(pathname, currentPath) + const routeFullPath = __VUEPRESS_CLEAN_URL__ + ? cleanRoutePath + : resolveRoutePathWithExt(cleanRoutePath) + hashAndQueries // the route not found // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- unsafe indexed access - if (!routes.value[routePath]) { + if (!routes.value[cleanRoutePath]) { return { - ...routes.value['/404.html'], + ...routes.value['/404'], path: routeFullPath, notFound: true, } as ResolvedRoute } return { - ...routes.value[routePath], + ...routes.value[cleanRoutePath], path: routeFullPath, notFound: false, } as ResolvedRoute diff --git a/packages/client/src/router/resolveRoutePath.ts b/packages/client/src/router/resolveRouteCleanPath.ts similarity index 96% rename from packages/client/src/router/resolveRoutePath.ts rename to packages/client/src/router/resolveRouteCleanPath.ts index 74a2620955..58cb4846e4 100644 --- a/packages/client/src/router/resolveRoutePath.ts +++ b/packages/client/src/router/resolveRouteCleanPath.ts @@ -4,7 +4,7 @@ import { redirects, routes } from '../internal/routes.js' /** * Resolve route path with given raw path */ -export const resolveRoutePath = ( +export const resolveRouteCleanPath = ( pathname: string, currentPath?: string, ): string => { diff --git a/packages/client/src/router/resolveRouteFullPath.ts b/packages/client/src/router/resolveRouteFullPath.ts index 694386d74b..8fd184f076 100644 --- a/packages/client/src/router/resolveRouteFullPath.ts +++ b/packages/client/src/router/resolveRouteFullPath.ts @@ -1,5 +1,5 @@ -import { splitPath } from '@vuepress/shared' -import { resolveRoutePath } from './resolveRoutePath.js' +import { resolveRoutePathWithExt, splitPath } from '@vuepress/shared' +import { resolveRouteCleanPath } from './resolveRouteCleanPath.js' /** * Resolve route full path with given raw path @@ -9,5 +9,11 @@ export const resolveRouteFullPath = ( currentPath?: string, ): string => { const { pathname, hashAndQueries } = splitPath(path) - return resolveRoutePath(pathname, currentPath) + hashAndQueries + const routeCleanPath = resolveRouteCleanPath(pathname, currentPath) + + return ( + (__VUEPRESS_CLEAN_URL__ + ? routeCleanPath + : resolveRoutePathWithExt(routeCleanPath)) + hashAndQueries + ) } diff --git a/packages/client/types.d.ts b/packages/client/types.d.ts index 032e27fcfd..89087a11fb 100644 --- a/packages/client/types.d.ts +++ b/packages/client/types.d.ts @@ -2,6 +2,7 @@ declare const __VUEPRESS_VERSION__: string declare const __VUEPRESS_BASE__: string declare const __VUEPRESS_DEV__: boolean +declare const __VUEPRESS_CLEAN_URL__: boolean declare const __VUEPRESS_SSR__: boolean declare const __VUE_HMR_RUNTIME__: Record declare const __VUE_PROD_DEVTOOLS__: boolean diff --git a/packages/core/src/app/resolveAppOptions.ts b/packages/core/src/app/resolveAppOptions.ts index 756227ffe5..6d6282db93 100644 --- a/packages/core/src/app/resolveAppOptions.ts +++ b/packages/core/src/app/resolveAppOptions.ts @@ -41,8 +41,18 @@ export const resolveAppOptions = ({ bundler, debug = false, markdown = {}, - pagePatterns = ['**/*.md', '!.vuepress', '!node_modules'], - permalinkPattern = null, + // eslint-disable-next-line @typescript-eslint/no-deprecated + pagePatterns: _pagePatterns, + // eslint-disable-next-line @typescript-eslint/no-deprecated + permalinkPattern: _permalinkPattern, + route: { + cleanUrl = false, + pagePatterns = ['**/*.md', '!.vuepress', '!node_modules'], + permalinkPattern = null, + } = { + pagePatterns: _pagePatterns, + permalinkPattern: _permalinkPattern, + }, plugins = [], theme, }: AppConfig): AppOptions => ({ @@ -68,8 +78,11 @@ export const resolveAppOptions = ({ bundler, debug, markdown, - pagePatterns, - permalinkPattern, + route: { + cleanUrl, + pagePatterns, + permalinkPattern, + }, plugins, theme, }) diff --git a/packages/core/src/app/resolveAppPages.ts b/packages/core/src/app/resolveAppPages.ts index 51ed100308..6c6af1b979 100644 --- a/packages/core/src/app/resolveAppPages.ts +++ b/packages/core/src/app/resolveAppPages.ts @@ -13,7 +13,7 @@ export const resolveAppPages = async (app: App): Promise => { log('resolveAppPages start') // resolve page absolute file paths according to the page patterns - const pageFilePaths = await globby(app.options.pagePatterns, { + const pageFilePaths = await globby(app.options.route.pagePatterns, { absolute: true, cwd: app.dir.source(), }) @@ -25,7 +25,7 @@ export const resolveAppPages = async (app: App): Promise => { pageFilePaths.map(async (filePath) => { const page = await createPage(app, { filePath }) // if there is a 404 page, set the default layout to NotFound - if (page.path === '/404.html') { + if (page.path === '/404') { page.frontmatter.layout ??= 'NotFound' hasNotFoundPage = true } @@ -37,7 +37,7 @@ export const resolveAppPages = async (app: App): Promise => { if (!hasNotFoundPage) { pages.push( await createPage(app, { - path: '/404.html', + path: '/404', frontmatter: { layout: 'NotFound' }, content: '404 Not Found', }), diff --git a/packages/core/src/page/resolvePagePath.ts b/packages/core/src/page/resolvePagePath.ts index 0edac439de..480533bd04 100644 --- a/packages/core/src/page/resolvePagePath.ts +++ b/packages/core/src/page/resolvePagePath.ts @@ -23,5 +23,9 @@ export const resolvePagePath = ({ ) } - return encodeURI(pagePath.split('/').map(sanitizeFileName).join('/')) + return ( + encodeURI(pagePath.split('/').map(sanitizeFileName).join('/')) + // get clean format + .replace(/\.html$/, '') + ) } diff --git a/packages/core/src/page/resolvePagePermalink.ts b/packages/core/src/page/resolvePagePermalink.ts index 4b62c069f7..9e59f56977 100644 --- a/packages/core/src/page/resolvePagePermalink.ts +++ b/packages/core/src/page/resolvePagePermalink.ts @@ -36,7 +36,7 @@ export const resolvePagePermalink = ({ } const permalinkPattern = - frontmatter.permalinkPattern || app.options.permalinkPattern + frontmatter.permalinkPattern || app.options.route.permalinkPattern if (!isString(permalinkPattern)) { return null diff --git a/packages/core/src/types/app/options.ts b/packages/core/src/types/app/options.ts index 74a11478f6..8681e61ccf 100644 --- a/packages/core/src/types/app/options.ts +++ b/packages/core/src/types/app/options.ts @@ -5,6 +5,27 @@ import type { Bundler } from '../bundler.js' import type { PluginConfig } from '../plugin.js' import type { Theme } from '../theme.js' +export interface RouteOptions { + /** + * Whether to use "clean url" + */ + cleanUrl?: boolean + + /** + * Patterns to match the markdown files as pages + * + * @default ['**\/*.md', '!.vuepress', '!node_modules'] + */ + pagePatterns?: string[] + + /** + * Pattern to generate permalink for pages + * + * @default null + */ + permalinkPattern?: string | null +} + /** * Vuepress app common config that shared between dev and build */ @@ -69,18 +90,9 @@ export interface AppConfigCommon extends Partial { markdown?: MarkdownOptions /** - * Patterns to match the markdown files as pages - * - * @default ['**\/*.md', '!.vuepress', '!node_modules'] + * Vuepress route options */ - pagePatterns?: string[] - - /** - * Pattern to generate permalink for pages - * - * @default null - */ - permalinkPattern?: string | null + route?: RouteOptions /** * Vuepress bundler @@ -177,11 +189,27 @@ export interface AppConfigBuild { * * It would be provided by user, typically via a config file. */ -export type AppConfig = AppConfigBuild & AppConfigCommon & AppConfigDev +export type AppConfig = AppConfigBuild & + AppConfigCommon & + AppConfigDev & { + /** + * @deprecated use `route.pagePatterns` instead + */ + pagePatterns?: string[] + + /** + * @deprecated use `route.permalinkPattern` instead + */ + permalinkPattern?: string | null + } /** * Vuepress app options that resolved from user config. * * It fills all optional fields with a default value. */ -export type AppOptions = Required +export type AppOptions = Required< + AppConfigBuild & AppConfigCommon & AppConfigDev +> & { + route: Required +} diff --git a/packages/core/tests/app/resolveAppOptions.spec.ts b/packages/core/tests/app/resolveAppOptions.spec.ts index 29b2d07d2d..9ed1ea01f4 100644 --- a/packages/core/tests/app/resolveAppOptions.spec.ts +++ b/packages/core/tests/app/resolveAppOptions.spec.ts @@ -30,8 +30,11 @@ it('should create app options with default values', () => { host: '0.0.0.0', port: 8080, open: false, - pagePatterns: ['**/*.md', '!.vuepress', '!node_modules'], - permalinkPattern: null, + route: { + cleanUrl: false, + pagePatterns: ['**/*.md', '!.vuepress', '!node_modules'], + permalinkPattern: null, + }, templateDev: path.normalize( require.resolve('@vuepress/client/templates/dev.html'), ), diff --git a/packages/core/tests/app/resolveAppPages.spec.ts b/packages/core/tests/app/resolveAppPages.spec.ts index 73abc40368..5f83b8bb7f 100644 --- a/packages/core/tests/app/resolveAppPages.spec.ts +++ b/packages/core/tests/app/resolveAppPages.spec.ts @@ -13,9 +13,9 @@ it('should create two pages with default 404 page', async () => { app.markdown = createMarkdown() const pages = await resolveAppPages(app) - const fooPage = pages.find((page) => page.path === '/foo.html') - const barPage = pages.find((page) => page.path === '/bar.html') - const notFoundPage = pages.find((page) => page.path === '/404.html') + const fooPage = pages.find((page) => page.path === '/foo') + const barPage = pages.find((page) => page.path === '/bar') + const notFoundPage = pages.find((page) => page.path === '/404') expect(pages).toHaveLength(3) expect(fooPage?.filePathRelative).toEqual('foo.md') @@ -33,9 +33,9 @@ it('should create two pages with custom 404 page', async () => { app.markdown = createMarkdown() const pages = await resolveAppPages(app) - const fooPage = pages.find((page) => page.path === '/foo.html') - const barPage = pages.find((page) => page.path === '/bar.html') - const notFoundPage = pages.find((page) => page.path === '/404.html') + const fooPage = pages.find((page) => page.path === '/foo') + const barPage = pages.find((page) => page.path === '/bar') + const notFoundPage = pages.find((page) => page.path === '/404') expect(pages).toHaveLength(3) expect(fooPage?.filePathRelative).toEqual('foo.md') diff --git a/packages/core/tests/page/inferPagePath.spec.ts b/packages/core/tests/page/inferPagePath.spec.ts index faac1b12b3..13419e2419 100644 --- a/packages/core/tests/page/inferPagePath.spec.ts +++ b/packages/core/tests/page/inferPagePath.spec.ts @@ -31,7 +31,7 @@ const TEST_CASES: [string, ReturnType][] = [ [ 'foo.md', { - pathInferred: '/foo.html', + pathInferred: '/foo', pathLocale: '/', }, ], @@ -45,7 +45,7 @@ const TEST_CASES: [string, ReturnType][] = [ [ 'en/foo.md', { - pathInferred: '/en/foo.html', + pathInferred: '/en/foo', pathLocale: '/en/', }, ], @@ -59,7 +59,7 @@ const TEST_CASES: [string, ReturnType][] = [ [ 'zh/foo.md', { - pathInferred: '/zh/foo.html', + pathInferred: '/zh/foo', pathLocale: '/zh/', }, ], @@ -73,7 +73,7 @@ const TEST_CASES: [string, ReturnType][] = [ [ '中文/foo.md', { - pathInferred: '/中文/foo.html', + pathInferred: '/中文/foo', pathLocale: '/中文/', }, ], @@ -99,7 +99,7 @@ it('should use `/` as the default locale path', () => { filePathRelative: 'en/foo/bar.md', }), ).toEqual({ - pathInferred: '/en/foo/bar.html', + pathInferred: '/en/foo/bar', pathLocale: '/', }) }) diff --git a/packages/core/tests/page/resolvePagePath.spec.ts b/packages/core/tests/page/resolvePagePath.spec.ts index 316d8c04e1..9ffb436e00 100644 --- a/packages/core/tests/page/resolvePagePath.spec.ts +++ b/packages/core/tests/page/resolvePagePath.spec.ts @@ -40,7 +40,7 @@ const TEST_CASES: [ }, }, ], - '/options.html', + '/options', ], // use permalink [ @@ -92,7 +92,7 @@ const TEST_CASES: [ options: {}, }, ], - '/inferred.html', + '/inferred', ], ] diff --git a/packages/markdown/src/plugins/linksPlugin/linksPlugin.ts b/packages/markdown/src/plugins/linksPlugin/linksPlugin.ts index 14c04eac46..2599b25665 100644 --- a/packages/markdown/src/plugins/linksPlugin/linksPlugin.ts +++ b/packages/markdown/src/plugins/linksPlugin/linksPlugin.ts @@ -1,10 +1,21 @@ -import { inferRoutePath, isLinkExternal } from '@vuepress/shared' +import { + inferRoutePath, + isLinkExternal, + resolveRoutePathWithExt, +} from '@vuepress/shared' import type { PluginWithOptions } from 'markdown-it' import type Token from 'markdown-it/lib/token.mjs' import type { MarkdownEnv } from '../../types.js' import { resolvePaths } from './resolvePaths.js' export interface LinksPluginOptions { + /** + * Whether use "clean url" + * + * @default false + */ + cleanUrl?: boolean + /** * Additional attributes for external links * @@ -47,6 +58,7 @@ export const linksPlugin: PluginWithOptions = ( const internalTag = options.internalTag || 'RouteLink' const isExternal = options.isExternal ?? ((href, env) => isLinkExternal(href, env.base)) + const cleanUrl = options.cleanUrl ?? false // attrs that going to be added to external links const externalAttrs = { @@ -128,14 +140,17 @@ export const linksPlugin: PluginWithOptions = ( ? absolutePath.replace(new RegExp(`^${base}`), '/') : relativePath, ) + // replace the original href link with the normalized path - hrefAttr[1] = `${normalizedPath}${rawHashAndQueries}` + hrefAttr[1] = `${cleanUrl ? normalizedPath : resolveRoutePathWithExt(normalizedPath)}${rawHashAndQueries}` // set `hasOpenInternalLink` to modify the ending tag hasOpenInternalLink = true } else { + // ext is added here const normalizedPath = inferRoutePath(absolutePath ?? relativePath) + // replace the original href link with the normalized path - hrefAttr[1] = `${normalizedPath}${rawHashAndQueries}` + hrefAttr[1] = `${cleanUrl ? normalizedPath : resolveRoutePathWithExt(normalizedPath)}${rawHashAndQueries}` } // extract internal links for file / page existence check diff --git a/packages/shared/src/utils/routes/index.ts b/packages/shared/src/utils/routes/index.ts index 5e67f4b001..e0558e4a4c 100644 --- a/packages/shared/src/utils/routes/index.ts +++ b/packages/shared/src/utils/routes/index.ts @@ -1,5 +1,6 @@ export * from './inferRoutePath' export * from './normalizeRoutePath.js' +export * from './resolveRoutePathWithExt.js' export * from './resolveLocalePath.js' export * from './resolveRoutePathFromUrl.js' export * from './splitPath.js' diff --git a/packages/shared/src/utils/routes/inferRoutePath.ts b/packages/shared/src/utils/routes/inferRoutePath.ts index 803cef50bf..775c174bcf 100644 --- a/packages/shared/src/utils/routes/inferRoutePath.ts +++ b/packages/shared/src/utils/routes/inferRoutePath.ts @@ -6,20 +6,20 @@ export const inferRoutePath = (rawPath: string): string => { if (!rawPath || rawPath.endsWith('/')) return rawPath // convert README.md to index.html - let routePath = rawPath.replace(/(^|\/)README.md$/i, '$1index.html') + let routePath = rawPath.replace(/(^|\/)README.md$/i, '$1index') - // convert /foo/bar.md to /foo/bar.html + // convert /foo/bar.md to /foo/bar if (routePath.endsWith('.md')) { - routePath = `${routePath.substring(0, routePath.length - 3)}.html` + routePath = routePath.substring(0, routePath.length - 3) } - // convert /foo/bar to /foo/bar.html - else if (!routePath.endsWith('.html')) { - routePath = `${routePath}.html` + // convert /foo/bar.html to /foo/bar + else if (routePath.endsWith('.html')) { + routePath = routePath.substring(0, routePath.length - 5) } - // convert /foo/index.html to /foo/ - if (routePath.endsWith('/index.html')) { - routePath = routePath.substring(0, routePath.length - 10) + // convert /foo/index to /foo/ + if (routePath.endsWith('/index')) { + routePath = routePath.substring(0, routePath.length - 5) } return routePath diff --git a/packages/shared/src/utils/routes/resolveRoutePathWithExt.ts b/packages/shared/src/utils/routes/resolveRoutePathWithExt.ts new file mode 100644 index 0000000000..cd48fb29b1 --- /dev/null +++ b/packages/shared/src/utils/routes/resolveRoutePathWithExt.ts @@ -0,0 +1,2 @@ +export const resolveRoutePathWithExt = (routePath: string): string => + routePath.endsWith('/') ? routePath : `${routePath}.html` diff --git a/packages/shared/tests/routes/inferRoutePath.spec.ts b/packages/shared/tests/routes/inferRoutePath.spec.ts index 8869932c13..4accf3b63e 100644 --- a/packages/shared/tests/routes/inferRoutePath.spec.ts +++ b/packages/shared/tests/routes/inferRoutePath.spec.ts @@ -15,19 +15,19 @@ const TEST_CASES = [ ['/foo/index.md', '/foo/'], ['/foo/index.html', '/foo/'], ['/foo/index', '/foo/'], - ['README.md', 'index.html'], - ['readme.md', 'index.html'], - ['index.md', 'index.html'], - ['index.html', 'index.html'], - ['index', 'index.html'], + ['README.md', 'index'], + ['readme.md', 'index'], + ['index.md', 'index'], + ['index.html', 'index'], + ['index', 'index'], // absolute non-index - ['/foo', '/foo.html'], - ['/foo.md', '/foo.html'], - ['/foo.html', '/foo.html'], - ['/foo/bar', '/foo/bar.html'], - ['/foo/bar.md', '/foo/bar.html'], - ['/foo/bar.html', '/foo/bar.html'], + ['/foo', '/foo'], + ['/foo.md', '/foo'], + ['/foo.html', '/foo'], + ['/foo/bar', '/foo/bar'], + ['/foo/bar.md', '/foo/bar'], + ['/foo/bar.html', '/foo/bar'], // relative index without current ['foo/', 'foo/'], @@ -38,19 +38,19 @@ const TEST_CASES = [ ['foo/index', 'foo/'], // relative non index without current - ['foo', 'foo.html'], - ['foo.md', 'foo.html'], - ['foo.html', 'foo.html'], - ['foo/bar', 'foo/bar.html'], - ['foo/bar.md', 'foo/bar.html'], - ['foo/bar.html', 'foo/bar.html'], + ['foo', 'foo'], + ['foo.md', 'foo'], + ['foo.html', 'foo'], + ['foo/bar', 'foo/bar'], + ['foo/bar.md', 'foo/bar'], + ['foo/bar.html', 'foo/bar'], // unexpected corner cases ['', ''], - ['.md', '.html'], - ['foo/.md', 'foo/.html'], - ['/.md', '/.html'], - ['/foo/.md', '/foo/.html'], + ['.md', ''], + ['foo/.md', 'foo/'], + ['/.md', '/'], + ['/foo/.md', '/foo/'], ] describe('should normalize clean paths correctly', () => { diff --git a/packages/shared/tests/routes/normalizeRoutePath.spec.ts b/packages/shared/tests/routes/normalizeRoutePath.spec.ts index 267b5c2731..197aa84461 100644 --- a/packages/shared/tests/routes/normalizeRoutePath.spec.ts +++ b/packages/shared/tests/routes/normalizeRoutePath.spec.ts @@ -1,7 +1,9 @@ import { describe, expect, it } from 'vitest' import { normalizeRoutePath } from '../../src/index.js' -const TEST_CASES = [ +type TestCase = [[path: string, current?: string], expected: string] + +const TEST_CASES: TestCase[] = [ // absolute index [['/'], '/'], [['/README.md'], '/'], @@ -15,19 +17,19 @@ const TEST_CASES = [ [['/foo/index.md'], '/foo/'], [['/foo/index.html'], '/foo/'], [['/foo/index'], '/foo/'], - [['README.md'], 'index.html'], - [['readme.md'], 'index.html'], - [['index.md'], 'index.html'], - [['index.html'], 'index.html'], - [['index'], 'index.html'], + [['README.md'], 'index'], + [['readme.md'], 'index'], + [['index.md'], 'index'], + [['index.html'], 'index'], + [['index'], 'index'], // absolute non-index - [['/foo'], '/foo.html'], - [['/foo.md'], '/foo.html'], - [['/foo.html'], '/foo.html'], - [['/foo/bar'], '/foo/bar.html'], - [['/foo/bar.md'], '/foo/bar.html'], - [['/foo/bar.html'], '/foo/bar.html'], + [['/foo'], '/foo'], + [['/foo.md'], '/foo'], + [['/foo.html'], '/foo'], + [['/foo/bar'], '/foo/bar'], + [['/foo/bar.md'], '/foo/bar'], + [['/foo/bar.html'], '/foo/bar'], // relative index without current [['foo/'], 'foo/'], @@ -38,164 +40,164 @@ const TEST_CASES = [ [['foo/index'], 'foo/'], // relative non index without current - [['foo'], 'foo.html'], - [['foo.md'], 'foo.html'], - [['foo.html'], 'foo.html'], - [['foo/bar'], 'foo/bar.html'], - [['foo/bar.md'], 'foo/bar.html'], - [['foo/bar.html'], 'foo/bar.html'], + [['foo'], 'foo'], + [['foo.md'], 'foo'], + [['foo.html'], 'foo'], + [['foo/bar'], 'foo/bar'], + [['foo/bar.md'], 'foo/bar'], + [['foo/bar.html'], 'foo/bar'], // relative non index with current - [['foo', '/'], '/foo.html'], - [['foo', '/a.html'], '/foo.html'], - [['foo', '/index.html'], '/foo.html'], - [['foo', '/a/'], '/a/foo.html'], - [['foo', '/a/index.html'], '/a/foo.html'], - [['foo', '/a/b.html'], '/a/foo.html'], - [['foo.md', '/'], '/foo.html'], - [['foo.md', '/a.html'], '/foo.html'], - [['foo.md', '/index.html'], '/foo.html'], - [['foo.md', '/a/'], '/a/foo.html'], - [['foo.md', '/a/index.html'], '/a/foo.html'], - [['foo.md', '/a/b.html'], '/a/foo.html'], - [['foo.html', '/'], '/foo.html'], - [['foo.html', '/a.html'], '/foo.html'], - [['foo.html', '/index.html'], '/foo.html'], - [['foo.html', '/a/'], '/a/foo.html'], - [['foo.html', '/a/index.html'], '/a/foo.html'], - [['foo.html', '/a/b.html'], '/a/foo.html'], - [['foo/bar', '/'], '/foo/bar.html'], - [['foo/bar', '/a.html'], '/foo/bar.html'], - [['foo/bar', '/index.html'], '/foo/bar.html'], - [['foo/bar', '/a/'], '/a/foo/bar.html'], - [['foo/bar', '/a/index.html'], '/a/foo/bar.html'], - [['foo/bar', '/a/b.html'], '/a/foo/bar.html'], - [['foo/bar.md', '/'], '/foo/bar.html'], - [['foo/bar.md', '/a.html'], '/foo/bar.html'], - [['foo/bar.md', '/index.html'], '/foo/bar.html'], - [['foo/bar.md', '/a/'], '/a/foo/bar.html'], - [['foo/bar.md', '/a/index.html'], '/a/foo/bar.html'], - [['foo/bar.md', '/a/b.html'], '/a/foo/bar.html'], - [['foo/bar.html', '/'], '/foo/bar.html'], - [['foo/bar.html', '/a.html'], '/foo/bar.html'], - [['foo/bar.html', '/index.html'], '/foo/bar.html'], - [['foo/bar.html', '/a/'], '/a/foo/bar.html'], - [['foo/bar.html', '/a/index.html'], '/a/foo/bar.html'], - [['foo/bar.html', '/a/b.html'], '/a/foo/bar.html'], - [['./foo', '/'], '/foo.html'], - [['./foo', '/a.html'], '/foo.html'], - [['./foo', '/index.html'], '/foo.html'], - [['./foo', '/a/'], '/a/foo.html'], - [['./foo', '/a/index.html'], '/a/foo.html'], - [['./foo', '/a/b.html'], '/a/foo.html'], - [['./foo.md', '/'], '/foo.html'], - [['./foo.md', '/a.html'], '/foo.html'], - [['./foo.md', '/index.html'], '/foo.html'], - [['./foo.md', '/a/'], '/a/foo.html'], - [['./foo.md', '/a/index.html'], '/a/foo.html'], - [['./foo.md', '/a/b.html'], '/a/foo.html'], - [['./foo.html', '/'], '/foo.html'], - [['./foo.html', '/a.html'], '/foo.html'], - [['./foo.html', '/index.html'], '/foo.html'], - [['./foo.html', '/a/'], '/a/foo.html'], - [['./foo.html', '/a/index.html'], '/a/foo.html'], - [['./foo.html', '/a/b.html'], '/a/foo.html'], - [['./foo/bar', '/'], '/foo/bar.html'], - [['./foo/bar', '/a.html'], '/foo/bar.html'], - [['./foo/bar', '/index.html'], '/foo/bar.html'], - [['./foo/bar', '/a/'], '/a/foo/bar.html'], - [['./foo/bar', '/a/index.html'], '/a/foo/bar.html'], - [['./foo/bar', '/a/b.html'], '/a/foo/bar.html'], - [['./foo/bar.md', '/'], '/foo/bar.html'], - [['./foo/bar.md', '/a.html'], '/foo/bar.html'], - [['./foo/bar.md', '/index.html'], '/foo/bar.html'], - [['./foo/bar.md', '/a/'], '/a/foo/bar.html'], - [['./foo/bar.md', '/a/index.html'], '/a/foo/bar.html'], - [['./foo/bar.md', '/a/b.html'], '/a/foo/bar.html'], - [['./foo/bar.html', '/'], '/foo/bar.html'], - [['./foo/bar.html', '/a.html'], '/foo/bar.html'], - [['./foo/bar.html', '/index.html'], '/foo/bar.html'], - [['./foo/bar.html', '/a/'], '/a/foo/bar.html'], - [['./foo/bar.html', '/a/index.html'], '/a/foo/bar.html'], - [['./foo/bar.html', '/a/b.html'], '/a/foo/bar.html'], - [['../foo', '/a/'], '/foo.html'], - [['../foo', '/a/index.html'], '/foo.html'], - [['../foo', '/a/b.html'], '/foo.html'], - [['../foo.md', '/a/'], '/foo.html'], - [['../foo.md', '/a/index.html'], '/foo.html'], - [['../foo.md', '/a/b.html'], '/foo.html'], - [['../foo.html', '/a/'], '/foo.html'], - [['../foo.html', '/a/index.html'], '/foo.html'], - [['../foo.html', '/a/b.html'], '/foo.html'], - [['../foo/bar', '/a/'], '/foo/bar.html'], - [['../foo/bar', '/a/index.html'], '/foo/bar.html'], - [['../foo/bar', '/a/b.html'], '/foo/bar.html'], - [['../foo/bar.md', '/a/'], '/foo/bar.html'], - [['../foo/bar.md', '/a/index.html'], '/foo/bar.html'], - [['../foo/bar.md', '/a/b.html'], '/foo/bar.html'], - [['../foo/bar.html', '/a/'], '/foo/bar.html'], - [['../foo/bar.html', '/a/index.html'], '/foo/bar.html'], - [['../foo/bar.html', '/a/b.html'], '/foo/bar.html'], + [['foo', '/'], '/foo'], + [['foo', '/a.html'], '/foo'], + [['foo', '/index.html'], '/foo'], + [['foo', '/a/'], '/a/foo'], + [['foo', '/a/index.html'], '/a/foo'], + [['foo', '/a/b.html'], '/a/foo'], + [['foo.md', '/'], '/foo'], + [['foo.md', '/a.html'], '/foo'], + [['foo.md', '/index.html'], '/foo'], + [['foo.md', '/a/'], '/a/foo'], + [['foo.md', '/a/index.html'], '/a/foo'], + [['foo.md', '/a/b.html'], '/a/foo'], + [['foo.html', '/'], '/foo'], + [['foo.html', '/a.html'], '/foo'], + [['foo.html', '/index.html'], '/foo'], + [['foo.html', '/a/'], '/a/foo'], + [['foo.html', '/a/index.html'], '/a/foo'], + [['foo.html', '/a/b.html'], '/a/foo'], + [['foo/bar', '/'], '/foo/bar'], + [['foo/bar', '/a.html'], '/foo/bar'], + [['foo/bar', '/index.html'], '/foo/bar'], + [['foo/bar', '/a/'], '/a/foo/bar'], + [['foo/bar', '/a/index.html'], '/a/foo/bar'], + [['foo/bar', '/a/b.html'], '/a/foo/bar'], + [['foo/bar.md', '/'], '/foo/bar'], + [['foo/bar.md', '/a.html'], '/foo/bar'], + [['foo/bar.md', '/index.html'], '/foo/bar'], + [['foo/bar.md', '/a/'], '/a/foo/bar'], + [['foo/bar.md', '/a/index.html'], '/a/foo/bar'], + [['foo/bar.md', '/a/b.html'], '/a/foo/bar'], + [['foo/bar.html', '/'], '/foo/bar'], + [['foo/bar.html', '/a.html'], '/foo/bar'], + [['foo/bar.html', '/index.html'], '/foo/bar'], + [['foo/bar.html', '/a/'], '/a/foo/bar'], + [['foo/bar.html', '/a/index.html'], '/a/foo/bar'], + [['foo/bar.html', '/a/b.html'], '/a/foo/bar'], + [['./foo', '/'], '/foo'], + [['./foo', '/a.html'], '/foo'], + [['./foo', '/index.html'], '/foo'], + [['./foo', '/a/'], '/a/foo'], + [['./foo', '/a/index.html'], '/a/foo'], + [['./foo', '/a/b.html'], '/a/foo'], + [['./foo.md', '/'], '/foo'], + [['./foo.md', '/a.html'], '/foo'], + [['./foo.md', '/index.html'], '/foo'], + [['./foo.md', '/a/'], '/a/foo'], + [['./foo.md', '/a/index.html'], '/a/foo'], + [['./foo.md', '/a/b.html'], '/a/foo'], + [['./foo.html', '/'], '/foo'], + [['./foo.html', '/a.html'], '/foo'], + [['./foo.html', '/index.html'], '/foo'], + [['./foo.html', '/a/'], '/a/foo'], + [['./foo.html', '/a/index.html'], '/a/foo'], + [['./foo.html', '/a/b.html'], '/a/foo'], + [['./foo/bar', '/'], '/foo/bar'], + [['./foo/bar', '/a.html'], '/foo/bar'], + [['./foo/bar', '/index.html'], '/foo/bar'], + [['./foo/bar', '/a/'], '/a/foo/bar'], + [['./foo/bar', '/a/index.html'], '/a/foo/bar'], + [['./foo/bar', '/a/b.html'], '/a/foo/bar'], + [['./foo/bar.md', '/'], '/foo/bar'], + [['./foo/bar.md', '/a.html'], '/foo/bar'], + [['./foo/bar.md', '/index.html'], '/foo/bar'], + [['./foo/bar.md', '/a/'], '/a/foo/bar'], + [['./foo/bar.md', '/a/index.html'], '/a/foo/bar'], + [['./foo/bar.md', '/a/b.html'], '/a/foo/bar'], + [['./foo/bar.html', '/'], '/foo/bar'], + [['./foo/bar.html', '/a.html'], '/foo/bar'], + [['./foo/bar.html', '/index.html'], '/foo/bar'], + [['./foo/bar.html', '/a/'], '/a/foo/bar'], + [['./foo/bar.html', '/a/index.html'], '/a/foo/bar'], + [['./foo/bar.html', '/a/b.html'], '/a/foo/bar'], + [['../foo', '/a/'], '/foo'], + [['../foo', '/a/index.html'], '/foo'], + [['../foo', '/a/b.html'], '/foo'], + [['../foo.md', '/a/'], '/foo'], + [['../foo.md', '/a/index.html'], '/foo'], + [['../foo.md', '/a/b.html'], '/foo'], + [['../foo.html', '/a/'], '/foo'], + [['../foo.html', '/a/index.html'], '/foo'], + [['../foo.html', '/a/b.html'], '/foo'], + [['../foo/bar', '/a/'], '/foo/bar'], + [['../foo/bar', '/a/index.html'], '/foo/bar'], + [['../foo/bar', '/a/b.html'], '/foo/bar'], + [['../foo/bar.md', '/a/'], '/foo/bar'], + [['../foo/bar.md', '/a/index.html'], '/foo/bar'], + [['../foo/bar.md', '/a/b.html'], '/foo/bar'], + [['../foo/bar.html', '/a/'], '/foo/bar'], + [['../foo/bar.html', '/a/index.html'], '/foo/bar'], + [['../foo/bar.html', '/a/b.html'], '/foo/bar'], // absolute non index with current - [['/foo', '/'], '/foo.html'], - [['/foo', '/a.html'], '/foo.html'], - [['/foo', '/index.html'], '/foo.html'], - [['/foo', '/a/'], '/foo.html'], - [['/foo', '/a/index.html'], '/foo.html'], - [['/foo', '/a/b.html'], '/foo.html'], - [['/foo.md', '/'], '/foo.html'], - [['/foo.md', '/a.html'], '/foo.html'], - [['/foo.md', '/index.html'], '/foo.html'], - [['/foo.md', '/a/'], '/foo.html'], - [['/foo.md', '/a/index.html'], '/foo.html'], - [['/foo.md', '/a/b.html'], '/foo.html'], - [['/foo.html', '/'], '/foo.html'], - [['/foo.html', '/a.html'], '/foo.html'], - [['/foo.html', '/index.html'], '/foo.html'], - [['/foo.html', '/a/'], '/foo.html'], - [['/foo.html', '/a/index.html'], '/foo.html'], - [['/foo.html', '/a/b.html'], '/foo.html'], - [['/foo/bar', '/'], '/foo/bar.html'], - [['/foo/bar', '/a.html'], '/foo/bar.html'], - [['/foo/bar', '/index.html'], '/foo/bar.html'], - [['/foo/bar', '/a/'], '/foo/bar.html'], - [['/foo/bar', '/a/index.html'], '/foo/bar.html'], - [['/foo/bar', '/a/b.html'], '/foo/bar.html'], - [['/foo/bar.md', '/'], '/foo/bar.html'], - [['/foo/bar.md', '/a.html'], '/foo/bar.html'], - [['/foo/bar.md', '/index.html'], '/foo/bar.html'], - [['/foo/bar.md', '/a/'], '/foo/bar.html'], - [['/foo/bar.md', '/a/index.html'], '/foo/bar.html'], - [['/foo/bar.md', '/a/b.html'], '/foo/bar.html'], - [['/foo/bar.html', '/'], '/foo/bar.html'], - [['/foo/bar.html', '/a.html'], '/foo/bar.html'], - [['/foo/bar.html', '/index.html'], '/foo/bar.html'], - [['/foo/bar.html', '/a/'], '/foo/bar.html'], - [['/foo/bar.html', '/a/index.html'], '/foo/bar.html'], - [['/foo/bar.html', '/a/b.html'], '/foo/bar.html'], + [['/foo', '/'], '/foo'], + [['/foo', '/a.html'], '/foo'], + [['/foo', '/index.html'], '/foo'], + [['/foo', '/a/'], '/foo'], + [['/foo', '/a/index.html'], '/foo'], + [['/foo', '/a/b.html'], '/foo'], + [['/foo.md', '/'], '/foo'], + [['/foo.md', '/a.html'], '/foo'], + [['/foo.md', '/index.html'], '/foo'], + [['/foo.md', '/a/'], '/foo'], + [['/foo.md', '/a/index.html'], '/foo'], + [['/foo.md', '/a/b.html'], '/foo'], + [['/foo.html', '/'], '/foo'], + [['/foo.html', '/a.html'], '/foo'], + [['/foo.html', '/index.html'], '/foo'], + [['/foo.html', '/a/'], '/foo'], + [['/foo.html', '/a/index.html'], '/foo'], + [['/foo.html', '/a/b.html'], '/foo'], + [['/foo/bar', '/'], '/foo/bar'], + [['/foo/bar', '/a.html'], '/foo/bar'], + [['/foo/bar', '/index.html'], '/foo/bar'], + [['/foo/bar', '/a/'], '/foo/bar'], + [['/foo/bar', '/a/index.html'], '/foo/bar'], + [['/foo/bar', '/a/b.html'], '/foo/bar'], + [['/foo/bar.md', '/'], '/foo/bar'], + [['/foo/bar.md', '/a.html'], '/foo/bar'], + [['/foo/bar.md', '/index.html'], '/foo/bar'], + [['/foo/bar.md', '/a/'], '/foo/bar'], + [['/foo/bar.md', '/a/index.html'], '/foo/bar'], + [['/foo/bar.md', '/a/b.html'], '/foo/bar'], + [['/foo/bar.html', '/'], '/foo/bar'], + [['/foo/bar.html', '/a.html'], '/foo/bar'], + [['/foo/bar.html', '/index.html'], '/foo/bar'], + [['/foo/bar.html', '/a/'], '/foo/bar'], + [['/foo/bar.html', '/a/index.html'], '/foo/bar'], + [['/foo/bar.html', '/a/b.html'], '/foo/bar'], // only hash and query [[''], ''], // unexpected corner cases - [['.md'], '.html'], - [['foo/.md'], 'foo/.html'], - [['/.md'], '/.html'], - [['/foo/.md'], '/foo/.html'], - [['.md', '/a/'], '/a/.html'], - [['foo/.md', '/a/'], '/a/foo/.html'], - [['/.md', '/a/'], '/.html'], - [['/foo/.md', '/a/'], '/foo/.html'], - [['.md', '/a/index.html'], '/a/.html'], - [['foo/.md', '/a/index.html'], '/a/foo/.html'], - [['/.md', '/a/index.html'], '/.html'], - [['/foo/.md', '/a/index.html'], '/foo/.html'], - [['.md', '/a/b.html'], '/a/.html'], - [['foo/.md', '/a/b.html'], '/a/foo/.html'], - [['/.md', '/a/b.html'], '/.html'], - [['/foo/.md', '/a/b.html'], '/foo/.html'], -] as const + [['.md'], ''], + [['foo/.md'], 'foo/'], + [['/.md'], '/'], + [['/foo/.md'], '/foo/'], + [['.md', '/a/'], '/a/'], + [['foo/.md', '/a/'], '/a/foo/'], + [['/.md', '/a/'], '/'], + [['/foo/.md', '/a/'], '/foo/'], + [['.md', '/a/index.html'], '/a/'], + [['foo/.md', '/a/index.html'], '/a/foo/'], + [['/.md', '/a/index.html'], '/'], + [['/foo/.md', '/a/index.html'], '/foo/'], + [['.md', '/a/b.html'], '/a/'], + [['foo/.md', '/a/b.html'], '/a/foo/'], + [['/.md', '/a/b.html'], '/'], + [['/foo/.md', '/a/b.html'], '/foo/'], +] describe('should normalize clean paths correctly', () => { TEST_CASES.forEach(([[path, current], expected]) => { From 3f4db30887c8b01daa90fc2b8fb1f90e6d0eb7c4 Mon Sep 17 00:00:00 2001 From: Mister-Hope Date: Thu, 20 Feb 2025 19:50:49 +0800 Subject: [PATCH 2/5] test: fix e2e --- e2e/tests/router/navigate-by-link.spec.ts | 153 +++++++++++++++++++--- 1 file changed, 132 insertions(+), 21 deletions(-) diff --git a/e2e/tests/router/navigate-by-link.spec.ts b/e2e/tests/router/navigate-by-link.spec.ts index 7ef3ae652b..17b93d8564 100644 --- a/e2e/tests/router/navigate-by-link.spec.ts +++ b/e2e/tests/router/navigate-by-link.spec.ts @@ -129,47 +129,90 @@ test.describe('markdown clean links', () => { const selector = '#markdown-clean-links + blockquote + ul > li > a' test('should navigate to home correctly', async ({ page }) => { - await page.locator(selector).nth(0).click() - await expect(page).toHaveURL(BASE) - await expect(page.locator('#home-h2')).toHaveText('Home H2') + const locator = page.locator(selector).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') + } }) test('should navigate to 404 page correctly', async ({ page }) => { - await page.locator(selector).nth(1).click() - await expect(page).toHaveURL(`${BASE}404.html`) - await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + const locator = page.locator(selector).nth(1) + + if (BASE === '/') { + await locator.click() + await expect(page).toHaveURL(`${BASE}404.html`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + } else { + await expect(locator).toHaveAttribute('href', '/404') + await expect(locator).toHaveAttribute('target', '_blank') + } }) test('should preserve query', async ({ page }) => { - await page.locator(selector).nth(2).click() - await expect(page).toHaveURL(`${BASE}?home=true`) - await expect(page.locator('#home-h2')).toHaveText('Home H2') + const locator = page.locator(selector).nth(2) + + if (BASE === '/') { + await locator.click() + await expect(page).toHaveURL(`${BASE}?home=true`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + } else { + await expect(locator).toHaveAttribute('href', '/?home=true') + await expect(locator).toHaveAttribute('target', '_blank') + } }) test('should preserve query and hash', async ({ page }) => { - await page.locator(selector).nth(3).click() - await expect(page).toHaveURL(`${BASE}?home=true#home`) - await expect(page.locator('#home-h2')).toHaveText('Home H2') + const locator = page.locator(selector).nth(3) + + if (BASE === '/') { + await locator.click() + await expect(page).toHaveURL(`${BASE}?home=true#home`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + } else { + await expect(locator).toHaveAttribute('href', '/?home=true#home') + await expect(locator).toHaveAttribute('target', '_blank') + } }) test('should preserve hash', async ({ page }) => { - await page.locator(selector).nth(4).click() - await expect(page).toHaveURL(`${BASE}404.html#404`) - await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + const locator = page.locator(selector).nth(4) + + if (BASE === '/') { + await locator.click() + await expect(page).toHaveURL(`${BASE}404.html#404`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + } else { + await expect(locator).toHaveAttribute('href', '/404#404') + await expect(locator).toHaveAttribute('target', '_blank') + } }) test('should preserve hash and query', async ({ page }) => { - await page.locator(selector).nth(5).click() - await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`) - await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + const locator = page.locator(selector).nth(5) + + if (BASE === '/') { + await locator.click() + await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + } else { + await expect(locator).toHaveAttribute('href', '/404#404?notFound=true') + await expect(locator).toHaveAttribute('target', '_blank') + } }) }) test.describe('markdown links with html paths', () => { + const selector = '#markdown-links-with-html-paths + blockquote + ul > li > a' + test('should navigate to home correctly', async ({ page }) => { - const locator = page - .locator('#markdown-links-with-html-paths + blockquote + ul > li > a') - .nth(0) + const locator = page.locator(selector).nth(0) + if (BASE === '/') { await locator.click() await expect(page).toHaveURL('/') @@ -179,4 +222,72 @@ test.describe('markdown links with html paths', () => { await expect(locator).toHaveAttribute('target', '_blank') } }) + + test('should navigate to 404 page correctly', async ({ page }) => { + const locator = page.locator(selector).nth(1) + + if (BASE === '/') { + await locator.click() + await expect(page).toHaveURL(`${BASE}404.html`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + } else { + await expect(locator).toHaveAttribute('href', '/404.html') + await expect(locator).toHaveAttribute('target', '_blank') + } + }) + + test('should preserve query', async ({ page }) => { + const locator = page.locator(selector).nth(2) + + if (BASE === '/') { + await locator.click() + await expect(page).toHaveURL(`${BASE}?home=true`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + } else { + await expect(locator).toHaveAttribute('href', '/?home=true') + await expect(locator).toHaveAttribute('target', '_blank') + } + }) + + test('should preserve query and hash', async ({ page }) => { + const locator = page.locator(selector).nth(3) + + if (BASE === '/') { + await locator.click() + await expect(page).toHaveURL(`${BASE}?home=true#home`) + await expect(page.locator('#home-h2')).toHaveText('Home H2') + } else { + await expect(locator).toHaveAttribute('href', '/?home=true#home') + await expect(locator).toHaveAttribute('target', '_blank') + } + }) + + test('should preserve hash', async ({ page }) => { + const locator = page.locator(selector).nth(4) + + if (BASE === '/') { + await locator.click() + await expect(page).toHaveURL(`${BASE}404.html#404`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + } else { + await expect(locator).toHaveAttribute('href', '/404.html#404') + await expect(locator).toHaveAttribute('target', '_blank') + } + }) + + test('should preserve hash and query', async ({ page }) => { + const locator = page.locator(selector).nth(5) + + if (BASE === '/') { + await locator.click() + await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`) + await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') + } else { + await expect(locator).toHaveAttribute( + 'href', + '/404.html#404?notFound=true', + ) + await expect(locator).toHaveAttribute('target', '_blank') + } + }) }) From abd92161931265f231a8801b8197f29cb91e2d68 Mon Sep 17 00:00:00 2001 From: Mister-Hope Date: Thu, 20 Feb 2025 20:05:10 +0800 Subject: [PATCH 3/5] chore: tweaks --- e2e/docs/components/route-link.md | 5 +- e2e/docs/router/navigate-by-link.md | 5 -- e2e/docs/router/navigate-by-router.md | 10 ---- e2e/tests/components/route-link.spec.ts | 5 +- e2e/tests/router/navigate-by-link.spec.ts | 47 ------------------- e2e/tests/router/navigate-by-router.spec.ts | 14 ------ .../tests/routes/normalizeRoutePath.spec.ts | 2 +- 7 files changed, 3 insertions(+), 85 deletions(-) diff --git a/e2e/docs/components/route-link.md b/e2e/docs/components/route-link.md index 48dd4e0010..fbe08217e3 100644 --- a/e2e/docs/components/route-link.md +++ b/e2e/docs/components/route-link.md @@ -60,26 +60,23 @@ - text - texttext2 -### Hash and query +### Query and hash - text - text - text - text - text -- text - text - text - text - text - text -- text - text - text - text - text - text -- text ### Relative diff --git a/e2e/docs/router/navigate-by-link.md b/e2e/docs/router/navigate-by-link.md index 5405b962e2..9eb6e96187 100644 --- a/e2e/docs/router/navigate-by-link.md +++ b/e2e/docs/router/navigate-by-link.md @@ -5,7 +5,6 @@ - [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 @@ -14,7 +13,6 @@ Home Home 404 -404 ## HTML Clean Links @@ -23,7 +21,6 @@ Home Home 404 -404 ## Markdown Clean Links @@ -34,7 +31,6 @@ - [Home with query](/?home=true) - [Home with query and hash](/?home=true#home) - [404 with hash](/404#404) -- [404 with hash and query](/404#404?notFound=true) ## Markdown Links with html paths @@ -45,4 +41,3 @@ - [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) diff --git a/e2e/docs/router/navigate-by-router.md b/e2e/docs/router/navigate-by-router.md index b96db71d21..b791ed7ea5 100644 --- a/e2e/docs/router/navigate-by-router.md +++ b/e2e/docs/router/navigate-by-router.md @@ -4,7 +4,6 @@ -
@@ -13,7 +12,6 @@ -
diff --git a/e2e/tests/components/route-link.spec.ts b/e2e/tests/components/route-link.spec.ts index e555744cac..0414420ea6 100644 --- a/e2e/tests/components/route-link.spec.ts +++ b/e2e/tests/components/route-link.spec.ts @@ -142,26 +142,23 @@ test('should render slots correctly', async ({ page }) => { } }) -test('should render hash and query correctly', async ({ page }) => { +test('should render query and hash correctly', async ({ page }) => { const CONFIGS = [ `${BASE}#hash`, `${BASE}?query`, `${BASE}?query#hash`, `${BASE}?query=1#hash`, `${BASE}?query=1&query=2#hash`, - `${BASE}#hash?query=1&query=2`, `${BASE}#hash`, `${BASE}?query`, `${BASE}?query#hash`, `${BASE}?query=1#hash`, `${BASE}?query=1&query=2#hash`, - `${BASE}#hash?query=1&query=2`, `#hash`, `?query`, `?query#hash`, `?query=1#hash`, `?query=1&query=2#hash`, - `#hash?query=1&query=2`, ] for (const [index, href] of CONFIGS.entries()) { diff --git a/e2e/tests/router/navigate-by-link.spec.ts b/e2e/tests/router/navigate-by-link.spec.ts index 17b93d8564..9fbea5c6a6 100644 --- a/e2e/tests/router/navigate-by-link.spec.ts +++ b/e2e/tests/router/navigate-by-link.spec.ts @@ -37,12 +37,6 @@ test.describe('markdown links', () => { 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(selector).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', () => { @@ -77,12 +71,6 @@ test.describe('html links', () => { 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(selector).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 clean links', () => { @@ -117,12 +105,6 @@ test.describe('html clean links', () => { 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(selector).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 clean links', () => { @@ -192,19 +174,6 @@ test.describe('markdown clean links', () => { await expect(locator).toHaveAttribute('target', '_blank') } }) - - test('should preserve hash and query', async ({ page }) => { - const locator = page.locator(selector).nth(5) - - if (BASE === '/') { - await locator.click() - await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`) - await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') - } else { - await expect(locator).toHaveAttribute('href', '/404#404?notFound=true') - await expect(locator).toHaveAttribute('target', '_blank') - } - }) }) test.describe('markdown links with html paths', () => { @@ -274,20 +243,4 @@ test.describe('markdown links with html paths', () => { await expect(locator).toHaveAttribute('target', '_blank') } }) - - test('should preserve hash and query', async ({ page }) => { - const locator = page.locator(selector).nth(5) - - if (BASE === '/') { - await locator.click() - await expect(page).toHaveURL(`${BASE}404.html#404?notFound=true`) - await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') - } else { - await expect(locator).toHaveAttribute( - 'href', - '/404.html#404?notFound=true', - ) - await expect(locator).toHaveAttribute('target', '_blank') - } - }) }) diff --git a/e2e/tests/router/navigate-by-router.spec.ts b/e2e/tests/router/navigate-by-router.spec.ts index cfe6b533a5..6af7fba0d6 100644 --- a/e2e/tests/router/navigate-by-router.spec.ts +++ b/e2e/tests/router/navigate-by-router.spec.ts @@ -74,17 +74,3 @@ test.describe('should preserve hash', () => { await expect(page.locator('#notfound-h2')).toHaveText('NotFound H2') }) }) - -test.describe('should preserve hash and query', () => { - test('full', async ({ page }) => { - await page.locator('#full .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') - }) - - test('clean', async ({ page }) => { - await page.locator('#clean .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') - }) -}) diff --git a/packages/shared/tests/routes/normalizeRoutePath.spec.ts b/packages/shared/tests/routes/normalizeRoutePath.spec.ts index 197aa84461..d20e9b4fad 100644 --- a/packages/shared/tests/routes/normalizeRoutePath.spec.ts +++ b/packages/shared/tests/routes/normalizeRoutePath.spec.ts @@ -177,7 +177,7 @@ const TEST_CASES: TestCase[] = [ [['/foo/bar.html', '/a/index.html'], '/foo/bar'], [['/foo/bar.html', '/a/b.html'], '/foo/bar'], - // only hash and query + // empty [[''], ''], // unexpected corner cases From 0634f5bd2ca491267ee4718e8490b32054d2b96c Mon Sep 17 00:00:00 2001 From: Mister-Hope Date: Fri, 21 Feb 2025 13:27:13 +0800 Subject: [PATCH 4/5] fix: fix e2e --- e2e/tests/components/route-link.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/tests/components/route-link.spec.ts b/e2e/tests/components/route-link.spec.ts index 0414420ea6..d577ca953e 100644 --- a/e2e/tests/components/route-link.spec.ts +++ b/e2e/tests/components/route-link.spec.ts @@ -163,7 +163,7 @@ test('should render query and hash correctly', async ({ page }) => { for (const [index, href] of CONFIGS.entries()) { await expect( - page.locator('.e2e-theme-content #hash-and-query + ul > li a').nth(index), + page.locator('.e2e-theme-content #query-and-hash + ul > li a').nth(index), ).toHaveAttribute('href', href) } }) From 749b99c270ecdacf39249474cf401f13fe669833 Mon Sep 17 00:00:00 2001 From: Mister-Hope Date: Fri, 21 Feb 2025 14:24:55 +0800 Subject: [PATCH 5/5] feat(core): set cleanUrl for linksPlugin --- packages/core/src/app/resolveAppMarkdown.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/core/src/app/resolveAppMarkdown.ts b/packages/core/src/app/resolveAppMarkdown.ts index d3b35b4ef2..c2d86edd67 100644 --- a/packages/core/src/app/resolveAppMarkdown.ts +++ b/packages/core/src/app/resolveAppMarkdown.ts @@ -8,6 +8,14 @@ import type { App } from '../types/index.js' * @internal */ export const resolveAppMarkdown = async (app: App): Promise => { + app.options.markdown.links ??= {} + + // links plugin is not disabled + if (app.options.markdown.links !== false) { + // set the cleanUrl option + app.options.markdown.links.cleanUrl = app.options.route.cleanUrl + } + // plugin hook: extendsMarkdownOptions await app.pluginApi.hooks.extendsMarkdownOptions.process( app.options.markdown,