diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 7f763acda8..44c2c7dc3a 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,6 +1,12 @@ module.exports = { root: true, extends: 'vuepress', + + // FIXME: This should be added to `eslint-config-vuepress` + globals: { + __VUEPRESS_CLEAN_URL__: 'readonly', + }, + overrides: [ { files: ['*.ts', '*.vue', '*.cts'], diff --git a/packages/bundler-vite/src/plugins/vuepressMainPlugin.ts b/packages/bundler-vite/src/plugins/vuepressMainPlugin.ts index 05a53d3b0d..9c73ea36a6 100644 --- a/packages/bundler-vite/src/plugins/vuepressMainPlugin.ts +++ b/packages/bundler-vite/src/plugins/vuepressMainPlugin.ts @@ -205,6 +205,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 3a603cb458..da9c4035ac 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 48dcee2060..10c1bf7ad7 100644 --- a/packages/cli/src/commands/dev/watchPageFiles.ts +++ b/packages/cli/src/commands/dev/watchPageFiles.ts @@ -41,7 +41,7 @@ export const watchPageFiles = (app: App): FSWatcher[] => { app.pages.forEach((page) => addDeps(page)) // 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/resolveRoutePath.ts b/packages/client/src/router/resolveRoutePath.ts index f5aece8f9b..47b2be32ca 100644 --- a/packages/client/src/router/resolveRoutePath.ts +++ b/packages/client/src/router/resolveRoutePath.ts @@ -1,6 +1,8 @@ import { normalizeRoutePath } from '@vuepress/shared' import { redirects, routes } from '../internal/routes.js' +declare const __VUEPRESS_CLEAN_URL__: boolean + /** * Resolve route path with given raw path */ @@ -9,7 +11,11 @@ export const resolveRoutePath = ( currentPath?: string, ): string => { // normalized path - const normalizedPath = normalizeRoutePath(path, currentPath) + const normalizedPath = normalizeRoutePath( + path, + currentPath, + __VUEPRESS_CLEAN_URL__, + ) if (routes.value[normalizedPath]) return normalizedPath // encoded path diff --git a/packages/client/types.d.ts b/packages/client/types.d.ts index 2d55050eb3..fdaa55b854 100644 --- a/packages/client/types.d.ts +++ b/packages/client/types.d.ts @@ -1,6 +1,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_OPTIONS_API__: boolean diff --git a/packages/core/src/app/prepare/prepareRoutes.ts b/packages/core/src/app/prepare/prepareRoutes.ts index fc7780be46..8250c788d8 100644 --- a/packages/core/src/app/prepare/prepareRoutes.ts +++ b/packages/core/src/app/prepare/prepareRoutes.ts @@ -23,13 +23,21 @@ if (import.meta.hot) { /** * Resolve page redirects */ -const resolvePageRedirects = ({ path, pathInferred }: Page): string[] => { +const resolvePageRedirects = ( + app: App, + { path, pathInferred }: Page, +): string[] => { // paths that should redirect to this page, use set to dedupe const redirectsSet = new Set() // add redirect to the set when the redirect could not be normalized & encoded to the page path const addRedirect = (redirect: string): void => { - const normalizedPath = normalizeRoutePath(redirect) + const normalizedPath = normalizeRoutePath( + redirect, + '', + app.options.route.cleanUrl, + ) + if (normalizedPath === path) return const encodedPath = encodeURI(normalizedPath) @@ -56,7 +64,10 @@ export const redirects = JSON.parse(${JSON.stringify( JSON.stringify( Object.fromEntries( app.pages.flatMap((page) => - resolvePageRedirects(page).map((redirect) => [redirect, page.path]), + resolvePageRedirects(app, page).map((redirect) => [ + redirect, + page.path, + ]), ), ), ), diff --git a/packages/core/src/app/resolveAppOptions.ts b/packages/core/src/app/resolveAppOptions.ts index d851c3d969..04538847b1 100644 --- a/packages/core/src/app/resolveAppOptions.ts +++ b/packages/core/src/app/resolveAppOptions.ts @@ -38,8 +38,16 @@ export const resolveAppOptions = ({ bundler, debug = false, markdown = {}, - pagePatterns = ['**/*.md', '!.vuepress', '!node_modules'], - permalinkPattern = null, + pagePatterns: _pagePatterns, + permalinkPattern: _permalinkPattern, + route: { + cleanUrl = false, + pagePatterns = ['**/*.md', '!.vuepress', '!node_modules'], + permalinkPattern = null, + } = { + pagePatterns: _pagePatterns, + permalinkPattern: _permalinkPattern, + }, plugins = [], theme, }: AppConfig): AppOptions => ({ @@ -65,8 +73,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 2f721fa7fd..4ac7447910 100644 --- a/packages/core/src/app/resolveAppPages.ts +++ b/packages/core/src/app/resolveAppPages.ts @@ -11,7 +11,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(), }) diff --git a/packages/core/src/page/inferPagePath.ts b/packages/core/src/page/inferPagePath.ts index fd2042755b..77055cdb17 100644 --- a/packages/core/src/page/inferPagePath.ts +++ b/packages/core/src/page/inferPagePath.ts @@ -23,9 +23,15 @@ export const inferPagePath = ({ // infer page route path from file path // foo/bar.md -> /foo/bar.html - const pathInferred = ensureLeadingSlash(filePathRelative) - .replace(/\.md$/, '.html') - .replace(/\/(README|index).html$/i, '/') + let pathInferred = ensureLeadingSlash(filePathRelative).replace( + /\/(README|index).md$/i, + '/', + ) + + if (pathInferred.endsWith('.md')) + pathInferred = + pathInferred.substring(0, pathInferred.length - 3) + + (app.options.route.cleanUrl ? '' : '.html') // resolve page locale path const pathLocale = resolveLocalePath(app.siteData.locales, pathInferred) diff --git a/packages/core/src/page/resolvePagePermalink.ts b/packages/core/src/page/resolvePagePermalink.ts index 6a549085f2..14ca26bb13 100644 --- a/packages/core/src/page/resolvePagePermalink.ts +++ b/packages/core/src/page/resolvePagePermalink.ts @@ -34,7 +34,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 93271fd4bf..6721a3b930 100644 --- a/packages/core/src/types/app/options.ts +++ b/packages/core/src/types/app/options.ts @@ -5,6 +5,12 @@ import type { Bundler } from '../bundler.js' import type { PluginConfig } from '../plugin.js' import type { Theme } from '../theme.js' +export interface RouteOptions { + cleanUrl?: boolean + pagePatterns?: string[] + permalinkPattern?: string | null +} + /** * Vuepress app common config that shared between dev and build */ @@ -14,11 +20,9 @@ export interface AppConfigCommon extends Partial { temp?: string cache?: string public?: string - debug?: boolean markdown?: MarkdownOptions - pagePatterns?: string[] - permalinkPattern?: string | null + route?: RouteOptions bundler: Bundler theme: Theme plugins?: PluginConfig @@ -95,9 +99,20 @@ export interface AppConfigBuild { /** * Vuepress app config */ -export type AppConfig = AppConfigCommon & AppConfigDev & AppConfigBuild +export type AppConfig = AppConfigCommon & + AppConfigDev & + AppConfigBuild & { + /** @deprecated use route.pagePatterns instead */ + pagePatterns?: string[] + /** @deprecated use route.permalinkPattern instead */ + permalinkPattern?: string | null + } /** * Vuepress app options */ -export type AppOptions = Required +export type AppOptions = Required< + AppConfigCommon & AppConfigDev & AppConfigBuild +> & { + route: Required +} diff --git a/packages/core/tests/app/resolveAppOptions.spec.ts b/packages/core/tests/app/resolveAppOptions.spec.ts index a7f3ff125c..1a269dfac6 100644 --- a/packages/core/tests/app/resolveAppOptions.spec.ts +++ b/packages/core/tests/app/resolveAppOptions.spec.ts @@ -30,8 +30,11 @@ describe('core > app > resolveAppOptions', () => { 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/shared/src/utils/index.ts b/packages/shared/src/utils/index.ts index 2d329ba2c3..ad1157637d 100644 --- a/packages/shared/src/utils/index.ts +++ b/packages/shared/src/utils/index.ts @@ -7,7 +7,6 @@ export * from './isLinkExternal.js' export * from './isLinkHttp.js' export * from './isLinkWithProtocol.js' export * from './isPlainObject.js' -export * from './inferRoutePath.js' export * from './normalizeRoutePath.js' export * from './omit.js' export * from './removeEndingSlash.js' diff --git a/packages/shared/src/utils/inferRoutePath.ts b/packages/shared/src/utils/inferRoutePath.ts index 0a1ba2e4aa..e23889f1fc 100644 --- a/packages/shared/src/utils/inferRoutePath.ts +++ b/packages/shared/src/utils/inferRoutePath.ts @@ -1,7 +1,7 @@ /** * Infer route path according to the given (markdown file) path */ -export const inferRoutePath = (path: string): string => { +export const inferRoutePath = (path: string, cleanUrl = false): string => { // if the pathname is empty or ends with `/`, return as is if (!path || path.endsWith('/')) return path @@ -21,6 +21,10 @@ export const inferRoutePath = (path: string): string => { if (routePath.endsWith('/index.html')) { routePath = routePath.substring(0, routePath.length - 10) } + // convert /foo/bar.html to /foo/bar if cleanUrl is enabled + else if (cleanUrl) { + routePath = routePath.substring(0, routePath.length - 5) + } return routePath } diff --git a/packages/shared/src/utils/normalizeRoutePath.ts b/packages/shared/src/utils/normalizeRoutePath.ts index 334fc308c1..da8bb2ed7d 100644 --- a/packages/shared/src/utils/normalizeRoutePath.ts +++ b/packages/shared/src/utils/normalizeRoutePath.ts @@ -5,17 +5,21 @@ const FAKE_HOST = 'http://.' /** * Normalize the given path to the final route path */ -export const normalizeRoutePath = (path: string, current?: string): string => { +export const normalizeRoutePath = ( + path: string, + current?: string, + cleanUrl = false, +): string => { if (!path.startsWith('/') && current) { // the relative path should be resolved against the current path const loc = current.slice(0, current.lastIndexOf('/')) const { pathname, search, hash } = new URL(`${loc}/${path}`, FAKE_HOST) - return inferRoutePath(pathname) + search + hash + return inferRoutePath(pathname, cleanUrl) + search + hash } const [pathname, ...queryAndHash] = path.split(/(\?|#)/) - return inferRoutePath(pathname) + queryAndHash.join('') + return inferRoutePath(pathname, cleanUrl) + queryAndHash.join('') } diff --git a/packages/shared/tests/inferRoutePath.spec.ts b/packages/shared/tests/inferRoutePath.spec.ts index 36bd910b96..5de26493c8 100644 --- a/packages/shared/tests/inferRoutePath.spec.ts +++ b/packages/shared/tests/inferRoutePath.spec.ts @@ -1,62 +1,122 @@ import { describe, expect, it } from 'vitest' import { inferRoutePath } from '../src/index.js' -const testCases = [ - // absolute index - ['/', '/'], - ['/README.md', '/'], - ['/readme.md', '/'], - ['/index.md', '/'], - ['/index.html', '/'], - ['/index', '/'], - ['/foo/', '/foo/'], - ['/foo/README.md', '/foo/'], - ['/foo/readme.md', '/foo/'], - ['/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'], - - // 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'], - - // relative index without current - ['foo/', 'foo/'], - ['foo/README.md', 'foo/'], - ['foo/readme.md', 'foo/'], - ['foo/index.md', 'foo/'], - ['foo/index.html', 'foo/'], - ['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'], - - // unexpected corner cases - ['', ''], - ['.md', '.html'], - ['foo/.md', 'foo/.html'], - ['/.md', '/.html'], - ['/foo/.md', '/foo/.html'], -] - describe('should normalize clean paths correctly', () => { + const testCases: [source: string, expected: string][] = [ + // absolute index + ['/', '/'], + ['/README.md', '/'], + ['/readme.md', '/'], + ['/index.md', '/'], + ['/index.html', '/'], + ['/index', '/'], + ['/foo/', '/foo/'], + ['/foo/README.md', '/foo/'], + ['/foo/readme.md', '/foo/'], + ['/foo/index.md', '/foo/'], + ['/foo/index.html', '/foo/'], + ['/foo/index', '/foo/'], + + // 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'], + + // relative index without current + ['README.md', 'index.html'], + ['readme.md', 'index.html'], + ['index.md', 'index.html'], + ['index.html', 'index.html'], + ['index', 'index.html'], + ['foo/', 'foo/'], + ['foo/README.md', 'foo/'], + ['foo/readme.md', 'foo/'], + ['foo/index.md', 'foo/'], + ['foo/index.html', 'foo/'], + ['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'], + + // unexpected corner cases + ['', ''], + ['.md', '.html'], + ['foo/.md', 'foo/.html'], + ['/.md', '/.html'], + ['/foo/.md', '/foo/.html'], + ] + testCases.forEach(([path, expected]) => it(`"${path}" -> "${expected}"`, () => { expect(inferRoutePath(path)).toBe(expected) }), ) }) + +describe('should normalize clean paths correctly', () => { + const testCases: [source: string, expected: string][] = [ + // absolute index + ['/', '/'], + ['/README.md', '/'], + ['/readme.md', '/'], + ['/index.md', '/'], + ['/index.html', '/'], + ['/index', '/'], + ['/foo/', '/foo/'], + ['/foo/README.md', '/foo/'], + ['/foo/readme.md', '/foo/'], + ['/foo/index.md', '/foo/'], + ['/foo/index.html', '/foo/'], + ['/foo/index', '/foo/'], + ['README.md', 'index'], + ['readme.md', 'index'], + ['index.md', 'index'], + ['index.html', 'index'], + ['index', 'index'], + + // absolute non-index + ['/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/'], + ['foo/README.md', 'foo/'], + ['foo/readme.md', 'foo/'], + ['foo/index.md', 'foo/'], + ['foo/index.html', 'foo/'], + ['foo/index', 'foo/'], + + // relative non index without current + ['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', ''], + ['foo/.md', 'foo/'], + ['/.md', '/'], + ['/foo/.md', '/foo/'], + ] + + testCases.forEach(([path, expected]) => + it(`"${path}" -> "${expected}"`, () => { + expect(inferRoutePath(path, true)).toBe(expected) + }), + ) +}) diff --git a/packages/shared/tests/normalizeRoutePath.spec.ts b/packages/shared/tests/normalizeRoutePath.spec.ts index 6543ab24e2..ecc3a1f113 100644 --- a/packages/shared/tests/normalizeRoutePath.spec.ts +++ b/packages/shared/tests/normalizeRoutePath.spec.ts @@ -1,245 +1,504 @@ import { describe, expect, it } from 'vitest' import { normalizeRoutePath } from '../src/index.js' -const testCases = [ - // absolute index - [['/'], '/'], - [['/README.md'], '/'], - [['/readme.md'], '/'], - [['/index.md'], '/'], - [['/index.html'], '/'], - [['/index'], '/'], - [['/foo/'], '/foo/'], - [['/foo/README.md'], '/foo/'], - [['/foo/readme.md'], '/foo/'], - [['/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'], - - // 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'], - - // relative index without current - [['foo/'], 'foo/'], - [['foo/README.md'], 'foo/'], - [['foo/readme.md'], 'foo/'], - [['foo/index.md'], 'foo/'], - [['foo/index.html'], 'foo/'], - [['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'], - - // 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'], - - // 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'], - - // 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'], -] - -describe('should normalize clean paths correctly', () => { - testCases.forEach(([[path, current], expected]) => - it(`${current ? `"${current}"-` : ''}"${path}" -> "${expected}"`, () => { - expect(normalizeRoutePath(path, current)).toBe(expected) - }), - ) -}) +describe('default', () => { + const testCases: [[source: string, current?: string], expected: string][] = [ + // absolute index + [['/'], '/'], + [['/README.md'], '/'], + [['/readme.md'], '/'], + [['/index.md'], '/'], + [['/index.html'], '/'], + [['/index'], '/'], + [['/foo/'], '/foo/'], + [['/foo/README.md'], '/foo/'], + [['/foo/readme.md'], '/foo/'], + [['/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'], -describe('should normalize paths with query correctly', () => { - testCases - .map(([[path, current], expected]) => [ - [`${path}?foo=bar`, current], - `${expected}?foo=bar`, - ]) - .forEach(([[path, current], expected]) => - it(`${current ? `"${current}"-` : ''}"${path}" -> "${expected}"`, () => { - expect(normalizeRoutePath(path, current)).toBe(expected) - }), - ) -}) + // 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'], + + // relative index without current + [['foo/'], 'foo/'], + [['foo/README.md'], 'foo/'], + [['foo/readme.md'], 'foo/'], + [['foo/index.md'], 'foo/'], + [['foo/index.html'], 'foo/'], + [['foo/index'], 'foo/'], -describe('should normalize paths with hash correctly', () => { - testCases - .map(([[path, current], expected]) => [ - [`${path}#foobar`, current], - `${expected}#foobar`, - ]) - .map(([[path, current], expected]) => + // 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'], + + // 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'], + + // 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'], + + // 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'], + ] + + describe('should normalize clean paths correctly', () => { + testCases.forEach(([[path, current], expected]) => it(`${current ? `"${current}"-` : ''}"${path}" -> "${expected}"`, () => { expect(normalizeRoutePath(path, current)).toBe(expected) }), ) + }) + + describe('should normalize paths with query correctly', () => { + testCases + .map<[[source: string, current?: string | undefined], expected: string]>( + ([[path, current], expected]) => [ + [`${path}?foo=bar`, current], + `${expected}?foo=bar`, + ], + ) + .forEach(([[path, current], expected]) => + it(`${current ? `"${current}"-` : ''}"${path}" -> "${expected}"`, () => { + expect(normalizeRoutePath(path, current)).toBe(expected) + }), + ) + }) + + describe('should normalize paths with hash correctly', () => { + testCases + .map<[[source: string, current?: string | undefined], expected: string]>( + ([[path, current], expected]) => [ + [`${path}#foobar`, current], + `${expected}#foobar`, + ], + ) + .map(([[path, current], expected]) => + it(`${current ? `"${current}"-` : ''}"${path}" -> "${expected}"`, () => { + expect(normalizeRoutePath(path, current)).toBe(expected) + }), + ) + }) + + describe('should normalize paths with query and hash correctly', () => { + testCases + .map<[[source: string, current?: string | undefined], expected: string]>( + ([[path, current], expected]) => [ + [`${path}?foo=1&bar=2#foobar`, current], + `${expected}?foo=1&bar=2#foobar`, + ], + ) + .map(([[path, current], expected]) => + it(`${current ? `"${current}"-` : ''}"${path}" -> "${expected}"`, () => { + expect(normalizeRoutePath(path, current)).toBe(expected) + }), + ) + }) }) -describe('should normalize paths with query and hash correctly', () => { - testCases - .map(([[path, current], expected]) => [ - [`${path}?foo=1&bar=2#foobar`, current], - `${expected}?foo=1&bar=2#foobar`, - ]) - .map(([[path, current], expected]) => +describe('cleanUrl', () => { + const testCases: [[source: string, current?: string], expected: string][] = [ + // absolute index + [['/'], '/'], + [['/README.md'], '/'], + [['/readme.md'], '/'], + [['/index.md'], '/'], + [['/index.html'], '/'], + [['/index'], '/'], + [['/foo/'], '/foo/'], + [['/foo/README.md'], '/foo/'], + [['/foo/readme.md'], '/foo/'], + [['/foo/index.md'], '/foo/'], + [['/foo/index.html'], '/foo/'], + [['/foo/index'], '/foo/'], + [['README.md'], 'index'], + [['readme.md'], 'index'], + [['index.md'], 'index'], + [['index.html'], 'index'], + [['index'], 'index'], + + // absolute non-index + [['/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/'], + [['foo/README.md'], 'foo/'], + [['foo/readme.md'], 'foo/'], + [['foo/index.md'], 'foo/'], + [['foo/index.html'], 'foo/'], + [['foo/index'], 'foo/'], + + // relative non index without current + [['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'], + [['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'], + [['/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'], ''], + [['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', () => { + testCases.forEach(([[path, current], expected]) => it(`${current ? `"${current}"-` : ''}"${path}" -> "${expected}"`, () => { - expect(normalizeRoutePath(path, current)).toBe(expected) + expect(normalizeRoutePath(path, current, true)).toBe(expected) }), ) + }) + + describe('should normalize paths with query correctly', () => { + testCases + .map<[[source: string, current?: string | undefined], expected: string]>( + ([[path, current], expected]) => [ + [`${path}?foo=bar`, current], + `${expected}?foo=bar`, + ], + ) + .forEach(([[path, current], expected]) => + it(`${current ? `"${current}"-` : ''}"${path}" -> "${expected}"`, () => { + expect(normalizeRoutePath(path, current, true)).toBe(expected) + }), + ) + }) + + describe('should normalize paths with hash correctly', () => { + testCases + .map<[[source: string, current?: string | undefined], expected: string]>( + ([[path, current], expected]) => [ + [`${path}#foobar`, current], + `${expected}#foobar`, + ], + ) + .map(([[path, current], expected]) => + it(`${current ? `"${current}"-` : ''}"${path}" -> "${expected}"`, () => { + expect(normalizeRoutePath(path, current, true)).toBe(expected) + }), + ) + }) + + describe('should normalize paths with query and hash correctly', () => { + testCases + .map<[[source: string, current?: string | undefined], expected: string]>( + ([[path, current], expected]) => [ + [`${path}?foo=1&bar=2#foobar`, current], + `${expected}?foo=1&bar=2#foobar`, + ], + ) + .map(([[path, current], expected]) => + it(`${current ? `"${current}"-` : ''}"${path}" -> "${expected}"`, () => { + expect(normalizeRoutePath(path, current, true)).toBe(expected) + }), + ) + }) })