diff --git a/e2e/docs/.vuepress/plugins/foo/fooPlugin.ts b/e2e/docs/.vuepress/plugins/foo/fooPlugin.ts index 93ef0c8cff..a6031278ff 100644 --- a/e2e/docs/.vuepress/plugins/foo/fooPlugin.ts +++ b/e2e/docs/.vuepress/plugins/foo/fooPlugin.ts @@ -1,8 +1,9 @@ +import type { Plugin } from 'vuepress/core' import { getDirname, path } from 'vuepress/utils' const __dirname = getDirname(import.meta.url) -export const fooPlugin = { +export const fooPlugin: Plugin = { name: 'test-plugin', clientConfigFile: path.resolve( __dirname, diff --git a/packages/cli/tests/config/loadUserConfig.spec.ts b/packages/cli/tests/config/loadUserConfig.spec.ts index 3327d9bd05..be905d5fb8 100644 --- a/packages/cli/tests/config/loadUserConfig.spec.ts +++ b/packages/cli/tests/config/loadUserConfig.spec.ts @@ -47,31 +47,29 @@ const MJS_CASES: [string, unknown][] = [ ], ] -describe('cli > config > loadUserConfig', () => { - describe('should load ts config file correctly', () => { - TS_CASES.forEach(([source, expected]) => { - it(JSON.stringify(source), async () => { - const { userConfig } = await loadUserConfig(source) - expect(userConfig).toEqual(expected) - }) +describe('should load ts config file correctly', () => { + TS_CASES.forEach(([source, expected]) => { + it(JSON.stringify(source), async () => { + const { userConfig } = await loadUserConfig(source) + expect(userConfig).toEqual(expected) }) }) +}) - describe('should load js config file correctly', () => { - JS_CASES.forEach(([source, expected]) => { - it(JSON.stringify(source), async () => { - const { userConfig } = await loadUserConfig(source) - expect(userConfig).toEqual(expected) - }) +describe('should load js config file correctly', () => { + JS_CASES.forEach(([source, expected]) => { + it(JSON.stringify(source), async () => { + const { userConfig } = await loadUserConfig(source) + expect(userConfig).toEqual(expected) }) }) +}) - describe('should load mjs config file correctly', () => { - MJS_CASES.forEach(([source, expected]) => { - it(JSON.stringify(source), async () => { - const { userConfig } = await loadUserConfig(source) - expect(userConfig).toEqual(expected) - }) +describe('should load mjs config file correctly', () => { + MJS_CASES.forEach(([source, expected]) => { + it(JSON.stringify(source), async () => { + const { userConfig } = await loadUserConfig(source) + expect(userConfig).toEqual(expected) }) }) }) diff --git a/packages/cli/tests/config/resolveUserConfigConventionalPath.spec.ts b/packages/cli/tests/config/resolveUserConfigConventionalPath.spec.ts index 808ac4ec19..4cd31fe353 100644 --- a/packages/cli/tests/config/resolveUserConfigConventionalPath.spec.ts +++ b/packages/cli/tests/config/resolveUserConfigConventionalPath.spec.ts @@ -14,13 +14,11 @@ const TEST_CASES: [string, string][] = [ [resolveFixtures('case6'), '.vuepress/config.mjs'], ] -describe('cli > config > resolveUserConfigConventionalPath', () => { - describe('should resolve conventional config file correctly', () => { - TEST_CASES.forEach(([source, expected]) => { - it(expected, () => { - const configFile = resolveUserConfigConventionalPath(source, source) - expect(configFile).toEqual(path.resolve(source, expected)) - }) +describe('should resolve conventional config file correctly', () => { + TEST_CASES.forEach(([source, expected]) => { + it(expected, () => { + const configFile = resolveUserConfigConventionalPath(source, source) + expect(configFile).toEqual(path.resolve(source, expected)) }) }) }) diff --git a/packages/cli/tests/config/resolveUserConfigPath.spec.ts b/packages/cli/tests/config/resolveUserConfigPath.spec.ts index 4fdc25f309..6daa4b490c 100644 --- a/packages/cli/tests/config/resolveUserConfigPath.spec.ts +++ b/packages/cli/tests/config/resolveUserConfigPath.spec.ts @@ -1,32 +1,30 @@ import { path } from '@vuepress/utils' -import { describe, expect, it, vi } from 'vitest' +import { expect, it, vi } from 'vitest' import { resolveUserConfigPath } from '../../src/index.js' const resolveFixtures = (str: string): string => path.resolve(__dirname, '../__fixtures__/config', str) -describe('cli > config > resolveUserConfigPath', () => { - it('should resolve absolute file path correctly', () => { - const absolutePath = resolveFixtures('custom-config.ts') - const configFile = resolveUserConfigPath(absolutePath) - expect(configFile).toEqual(absolutePath) - }) +it('should resolve absolute file path correctly', () => { + const absolutePath = resolveFixtures('custom-config.ts') + const configFile = resolveUserConfigPath(absolutePath) + expect(configFile).toEqual(absolutePath) +}) - it('should resolve relative file path correctly', () => { - const relativePath = 'custom-config.ts' - const configFile = resolveUserConfigPath(relativePath, resolveFixtures('')) - expect(configFile).toEqual(resolveFixtures(relativePath)) - }) +it('should resolve relative file path correctly', () => { + const relativePath = 'custom-config.ts' + const configFile = resolveUserConfigPath(relativePath, resolveFixtures('')) + expect(configFile).toEqual(resolveFixtures(relativePath)) +}) - it('should throw an error if file does not exist', () => { - const consoleError = console.error - console.error = vi.fn() +it('should throw an error if file does not exist', () => { + const consoleError = console.error + console.error = vi.fn() - expect(() => { - resolveUserConfigPath('4-0-4') - }).toThrow() - expect(console.error).toHaveBeenCalled() + expect(() => { + resolveUserConfigPath('4-0-4') + }).toThrow() + expect(console.error).toHaveBeenCalled() - console.error = consoleError - }) + console.error = consoleError }) diff --git a/packages/core/src/app/appInit.ts b/packages/core/src/app/appInit.ts index ac485cfe65..1e9f65b48c 100644 --- a/packages/core/src/app/appInit.ts +++ b/packages/core/src/app/appInit.ts @@ -9,6 +9,8 @@ const log = debug('vuepress:core/app') * Initialize a vuepress app * * Plugins should be used before initialization. + * + * @internal */ export const appInit = async (app: App): Promise => { log('init start') diff --git a/packages/core/src/app/appPrepare.ts b/packages/core/src/app/appPrepare.ts index a41f8d72be..4490332677 100644 --- a/packages/core/src/app/appPrepare.ts +++ b/packages/core/src/app/appPrepare.ts @@ -16,6 +16,8 @@ const log = debug('vuepress:core/app') * - routes * - site data * - other files that generated by plugins + * + * @internal */ export const appPrepare = async (app: App): Promise => { log('prepare start') diff --git a/packages/core/src/app/appUse.ts b/packages/core/src/app/appUse.ts index df53e43cab..9c98443a39 100644 --- a/packages/core/src/app/appUse.ts +++ b/packages/core/src/app/appUse.ts @@ -4,6 +4,13 @@ import { resolvePluginObject } from './resolvePluginObject.js' const log = debug('vuepress:core/app') +/** + * Use a plugin in vuepress app. + * + * Should be called before initialization. + * + * @internal + */ export const appUse = (app: App, rawPlugin: Plugin): App => { const pluginObject = resolvePluginObject(app, rawPlugin) diff --git a/packages/core/src/app/createBaseApp.ts b/packages/core/src/app/createBaseApp.ts index 3e91a42eff..dc8b18750d 100644 --- a/packages/core/src/app/createBaseApp.ts +++ b/packages/core/src/app/createBaseApp.ts @@ -1,5 +1,10 @@ import { createPluginApi } from '../pluginApi/index.js' -import type { App, AppConfig, Plugin } from '../types/index.js' +import type { + App, + AppConfig, + AppPropertiesBase, + Plugin, +} from '../types/index.js' import { appInit } from './appInit.js' import { appPrepare } from './appPrepare.js' import { appUse } from './appUse.js' @@ -12,12 +17,16 @@ import { resolveAppWriteTemp } from './resolveAppWriteTemp.js' import { setupAppThemeAndPlugins } from './setupAppThemeAndPlugins.js' /** - * Create vuepress app + * Create base vuepress app. + * + * Notice that the base app could not be used for dev nor build. + * + * It would be used for creating dev app or build app, or for testing. */ -export const createBaseApp = (config: AppConfig, isBuild = false): App => { +export const createBaseApp = (config: AppConfig): App => { const options = resolveAppOptions(config) const dir = resolveAppDir(options) - const env = resolveAppEnv(options, isBuild) + const env = resolveAppEnv(options) const pluginApi = createPluginApi() const siteData = resolveAppSiteData(options) const version = resolveAppVersion() @@ -38,7 +47,7 @@ export const createBaseApp = (config: AppConfig, isBuild = false): App => { use: (plugin: Plugin) => appUse(app, plugin), init: async () => appInit(app), prepare: async () => appPrepare(app), - } as App + } satisfies AppPropertiesBase as App // setup theme and plugins // notice that we setup theme before plugins, diff --git a/packages/core/src/app/createBuildApp.ts b/packages/core/src/app/createBuildApp.ts index 36830cbdf8..13e1651b8e 100644 --- a/packages/core/src/app/createBuildApp.ts +++ b/packages/core/src/app/createBuildApp.ts @@ -2,10 +2,14 @@ import type { AppConfig, BuildApp } from '../types/index.js' import { createBaseApp } from './createBaseApp.js' /** - * Create vuepress build app + * Create vuepress build app. */ export const createBuildApp = (config: AppConfig): BuildApp => { - const app = createBaseApp(config, true) as BuildApp + const app = createBaseApp(config) as BuildApp + + // set env flag and add build method + app.env.isBuild = true app.build = async () => app.options.bundler.build(app) + return app } diff --git a/packages/core/src/app/createDevApp.ts b/packages/core/src/app/createDevApp.ts index 59fe0f2ea0..3d21ea35ed 100644 --- a/packages/core/src/app/createDevApp.ts +++ b/packages/core/src/app/createDevApp.ts @@ -2,10 +2,14 @@ import type { AppConfig, DevApp } from '../types/index.js' import { createBaseApp } from './createBaseApp.js' /** - * Create vuepress dev app + * Create vuepress dev app. */ export const createDevApp = (config: AppConfig): DevApp => { - const app = createBaseApp(config, false) as DevApp + const app = createBaseApp(config) as DevApp + + // set env flag and add dev method + app.env.isDev = true app.dev = async () => app.options.bundler.dev(app) + return app } diff --git a/packages/core/src/app/prepare/prepareClientConfigs.ts b/packages/core/src/app/prepare/prepareClientConfigs.ts index 59bb1f5624..4636bd5556 100644 --- a/packages/core/src/app/prepare/prepareClientConfigs.ts +++ b/packages/core/src/app/prepare/prepareClientConfigs.ts @@ -2,6 +2,8 @@ import type { App } from '../../types/index.js' /** * Generate client configs temp file + * + * @internal */ export const prepareClientConfigs = async (app: App): Promise => { // plugin hook: clientConfigFile diff --git a/packages/core/src/app/prepare/prepareRoutes.ts b/packages/core/src/app/prepare/prepareRoutes.ts index d449d237d5..e8bf45a7bb 100644 --- a/packages/core/src/app/prepare/prepareRoutes.ts +++ b/packages/core/src/app/prepare/prepareRoutes.ts @@ -21,6 +21,8 @@ if (import.meta.hot) { /** * Resolve page redirects + * + * @internal */ const resolvePageRedirects = ({ path, pathInferred }: Page): string[] => { // paths that should redirect to this page, use set to dedupe diff --git a/packages/core/src/app/prepare/prepareSiteData.ts b/packages/core/src/app/prepare/prepareSiteData.ts index af0027fdd9..68976cceb3 100644 --- a/packages/core/src/app/prepare/prepareSiteData.ts +++ b/packages/core/src/app/prepare/prepareSiteData.ts @@ -17,6 +17,8 @@ if (import.meta.hot) { /** * Generate site data temp file + * + * @internal */ export const prepareSiteData = async (app: App): Promise => { let content = `\ diff --git a/packages/core/src/app/resolveAppDir.ts b/packages/core/src/app/resolveAppDir.ts index 5e78b9d344..18819dca9d 100644 --- a/packages/core/src/app/resolveAppDir.ts +++ b/packages/core/src/app/resolveAppDir.ts @@ -6,6 +6,8 @@ const require = createRequire(import.meta.url) /** * Create directory util function + * + * @internal */ export const createAppDirFunction = (baseDir: string): AppDirFunction => diff --git a/packages/core/src/app/resolveAppEnv.ts b/packages/core/src/app/resolveAppEnv.ts index dd7da3011d..d0b069c6a4 100644 --- a/packages/core/src/app/resolveAppEnv.ts +++ b/packages/core/src/app/resolveAppEnv.ts @@ -2,12 +2,11 @@ import type { AppEnv, AppOptions } from '../types/index.js' /** * Resolve environment flags for vuepress app + * + * @internal */ -export const resolveAppEnv = ( - options: AppOptions, - isBuild: boolean, -): AppEnv => ({ - isBuild, - isDev: !isBuild, +export const resolveAppEnv = (options: AppOptions): AppEnv => ({ + isBuild: false, + isDev: false, isDebug: options.debug, }) diff --git a/packages/core/src/app/resolveAppMarkdown.ts b/packages/core/src/app/resolveAppMarkdown.ts index 84ce7d0a1a..d3b35b4ef2 100644 --- a/packages/core/src/app/resolveAppMarkdown.ts +++ b/packages/core/src/app/resolveAppMarkdown.ts @@ -4,6 +4,8 @@ import type { App } from '../types/index.js' /** * Resolve markdown-it instance for vuepress app + * + * @internal */ export const resolveAppMarkdown = async (app: App): Promise => { // plugin hook: extendsMarkdownOptions diff --git a/packages/core/src/app/resolveAppOptions.ts b/packages/core/src/app/resolveAppOptions.ts index d851c3d969..756227ffe5 100644 --- a/packages/core/src/app/resolveAppOptions.ts +++ b/packages/core/src/app/resolveAppOptions.ts @@ -6,6 +6,8 @@ const require = createRequire(import.meta.url) /** * Create app options with default values + * + * @internal */ export const resolveAppOptions = ({ // site config @@ -34,6 +36,7 @@ export const resolveAppOptions = ({ templateBuild = path.normalize( require.resolve('@vuepress/client/templates/build.html'), ), + templateBuildRenderer = templateRenderer, // common config bundler, debug = false, @@ -61,7 +64,7 @@ export const resolveAppOptions = ({ shouldPreload, shouldPrefetch, templateBuild, - templateBuildRenderer: templateRenderer, + templateBuildRenderer, bundler, debug, markdown, diff --git a/packages/core/src/app/resolveAppPages.ts b/packages/core/src/app/resolveAppPages.ts index 33c280867b..0380d2e45b 100644 --- a/packages/core/src/app/resolveAppPages.ts +++ b/packages/core/src/app/resolveAppPages.ts @@ -6,6 +6,8 @@ const log = debug('vuepress:core/app') /** * Resolve pages for vuepress app + * + * @internal */ export const resolveAppPages = async ( app: App, diff --git a/packages/core/src/app/resolveAppSiteData.ts b/packages/core/src/app/resolveAppSiteData.ts index 9f2b654e3e..bc37720131 100644 --- a/packages/core/src/app/resolveAppSiteData.ts +++ b/packages/core/src/app/resolveAppSiteData.ts @@ -4,6 +4,8 @@ import type { AppOptions, SiteData } from '../types/index.js' * Resolve site data for vuepress app * * Site data will also be used in client + * + * @internal */ export const resolveAppSiteData = (options: AppOptions): SiteData => ({ base: options.base, diff --git a/packages/core/src/app/resolveAppVersion.ts b/packages/core/src/app/resolveAppVersion.ts index 590340ffc4..5f356432a3 100644 --- a/packages/core/src/app/resolveAppVersion.ts +++ b/packages/core/src/app/resolveAppVersion.ts @@ -5,6 +5,8 @@ const require = createRequire(import.meta.url) /** * Resolve version of vuepress app + * + * @internal */ export const resolveAppVersion = (): string => { const pkgJson = fs.readJsonSync( diff --git a/packages/core/src/app/resolveAppWriteTemp.ts b/packages/core/src/app/resolveAppWriteTemp.ts index f4e3b6c7d5..2a3560f072 100644 --- a/packages/core/src/app/resolveAppWriteTemp.ts +++ b/packages/core/src/app/resolveAppWriteTemp.ts @@ -3,6 +3,8 @@ import type { AppDir, AppWriteTemp } from '../types/index.js' /** * Resolve write temp file util for vuepress app + * + * @internal */ export const resolveAppWriteTemp = (dir: AppDir): AppWriteTemp => { const writeTemp: AppWriteTemp = async (file: string, content: string) => { diff --git a/packages/core/src/app/resolvePluginObject.ts b/packages/core/src/app/resolvePluginObject.ts index 8ccdd72aaa..4636f48ac0 100644 --- a/packages/core/src/app/resolvePluginObject.ts +++ b/packages/core/src/app/resolvePluginObject.ts @@ -3,6 +3,8 @@ import type { App, Plugin, PluginObject } from '../types/index.js' /** * Resolve a plugin object according to name / path / module and config + * + * @internal */ export const resolvePluginObject = ( app: App, diff --git a/packages/core/src/app/resolveThemeInfo.ts b/packages/core/src/app/resolveThemeInfo.ts index 34e9ff3b0d..b16c371429 100644 --- a/packages/core/src/app/resolveThemeInfo.ts +++ b/packages/core/src/app/resolveThemeInfo.ts @@ -3,6 +3,8 @@ import { resolvePluginObject } from './resolvePluginObject.js' /** * Resolve theme info and its parent theme info + * + * @internal */ export const resolveThemeInfo = (app: App, theme: Theme): ThemeInfo => { // resolve current theme info diff --git a/packages/core/src/app/setupAppThemeAndPlugins.ts b/packages/core/src/app/setupAppThemeAndPlugins.ts index f28c9f04e7..39aceda92b 100644 --- a/packages/core/src/app/setupAppThemeAndPlugins.ts +++ b/packages/core/src/app/setupAppThemeAndPlugins.ts @@ -3,6 +3,8 @@ import { resolveThemeInfo } from './resolveThemeInfo.js' /** * Setup theme and plugins for vuepress app + * + * @internal */ export const setupAppThemeAndPlugins = (app: App, config: AppConfig): void => { // recursively resolve theme info diff --git a/packages/core/src/page/createPage.ts b/packages/core/src/page/createPage.ts index 9965abf4a4..1200cace41 100644 --- a/packages/core/src/page/createPage.ts +++ b/packages/core/src/page/createPage.ts @@ -12,6 +12,9 @@ import { resolvePagePermalink } from './resolvePagePermalink.js' import { resolvePageRouteMeta } from './resolvePageRouteMeta.js' import { resolvePageSlug } from './resolvePageSlug.js' +/** + * Create vuepress page object + */ export const createPage = async ( app: App, options: PageOptions, diff --git a/packages/core/src/page/inferPagePath.ts b/packages/core/src/page/inferPagePath.ts index 8d082ea93c..2a406c7b66 100644 --- a/packages/core/src/page/inferPagePath.ts +++ b/packages/core/src/page/inferPagePath.ts @@ -7,6 +7,8 @@ import type { App } from '../types/index.js' /** * Infer page path according to file path + * + * @internal */ export const inferPagePath = ({ app, diff --git a/packages/core/src/page/parsePageContent.ts b/packages/core/src/page/parsePageContent.ts index 8af9d92b96..03e036a87e 100644 --- a/packages/core/src/page/parsePageContent.ts +++ b/packages/core/src/page/parsePageContent.ts @@ -9,6 +9,8 @@ import type { App, PageFrontmatter, PageOptions } from '../types/index.js' /** * Render page content and extract related info + * + * @internal */ export const parsePageContent = ({ app, diff --git a/packages/core/src/page/resolvePageChunkInfo.ts b/packages/core/src/page/resolvePageChunkInfo.ts index b297794c04..39b9945ef9 100644 --- a/packages/core/src/page/resolvePageChunkInfo.ts +++ b/packages/core/src/page/resolvePageChunkInfo.ts @@ -3,6 +3,8 @@ import type { App } from '../types/index.js' /** * Resolve page chunk file relative info + * + * @internal */ export const resolvePageChunkInfo = ({ app, diff --git a/packages/core/src/page/resolvePageContent.ts b/packages/core/src/page/resolvePageContent.ts index c4bda0732b..51a1aa3ab2 100644 --- a/packages/core/src/page/resolvePageContent.ts +++ b/packages/core/src/page/resolvePageContent.ts @@ -9,6 +9,8 @@ const FALLBACK_CONTENT = '' /** * Resolve page content according to `content` or `filePath` + * + * @internal */ export const resolvePageContent = async ({ filePath, diff --git a/packages/core/src/page/resolvePageDate.ts b/packages/core/src/page/resolvePageDate.ts index d371062f68..f9b2d5312c 100644 --- a/packages/core/src/page/resolvePageDate.ts +++ b/packages/core/src/page/resolvePageDate.ts @@ -10,6 +10,8 @@ const DEFAULT_DATE = '0000-00-00' * Resolve page date according to frontmatter or file path * * It will be resolved as 'yyyy-MM-dd' format + * + * @internal */ export const resolvePageDate = ({ frontmatter, diff --git a/packages/core/src/page/resolvePageFilePath.ts b/packages/core/src/page/resolvePageFilePath.ts index ee5a1b01ed..32d34e23a5 100644 --- a/packages/core/src/page/resolvePageFilePath.ts +++ b/packages/core/src/page/resolvePageFilePath.ts @@ -3,6 +3,8 @@ import type { App, PageOptions } from '../types/index.js' /** * Resolve absolute and relative path of page file + * + * @internal */ export const resolvePageFilePath = ({ app, diff --git a/packages/core/src/page/resolvePageHtmlInfo.ts b/packages/core/src/page/resolvePageHtmlInfo.ts index 677921ed04..0417e4edbe 100644 --- a/packages/core/src/page/resolvePageHtmlInfo.ts +++ b/packages/core/src/page/resolvePageHtmlInfo.ts @@ -3,6 +3,8 @@ import type { App } from '../types/index.js' /** * Resolve page rendered html file path + * + * @internal */ export const resolvePageHtmlInfo = ({ app, diff --git a/packages/core/src/page/resolvePageLang.ts b/packages/core/src/page/resolvePageLang.ts index 3f2f243256..d0774e871c 100644 --- a/packages/core/src/page/resolvePageLang.ts +++ b/packages/core/src/page/resolvePageLang.ts @@ -3,6 +3,8 @@ import type { App, PageFrontmatter } from '../types/index.js' /** * Resolve language of page + * + * @internal */ export const resolvePageLang = ({ app, diff --git a/packages/core/src/page/resolvePagePath.ts b/packages/core/src/page/resolvePagePath.ts index c40e366455..0edac439de 100644 --- a/packages/core/src/page/resolvePagePath.ts +++ b/packages/core/src/page/resolvePagePath.ts @@ -3,6 +3,8 @@ import type { PageOptions } from '../types/index.js' /** * Resolve the final route path of a page + * + * @internal */ export const resolvePagePath = ({ permalink, diff --git a/packages/core/src/page/resolvePagePermalink.ts b/packages/core/src/page/resolvePagePermalink.ts index 15a421dec1..4b62c069f7 100644 --- a/packages/core/src/page/resolvePagePermalink.ts +++ b/packages/core/src/page/resolvePagePermalink.ts @@ -4,6 +4,8 @@ import type { App, PageFrontmatter } from '../types/index.js' /** * Resolve page permalink from frontmatter / options / pattern + * + * @internal */ export const resolvePagePermalink = ({ app, diff --git a/packages/core/src/page/resolvePageRouteMeta.ts b/packages/core/src/page/resolvePageRouteMeta.ts index 76e5030a91..fa66e39d36 100644 --- a/packages/core/src/page/resolvePageRouteMeta.ts +++ b/packages/core/src/page/resolvePageRouteMeta.ts @@ -2,6 +2,8 @@ import type { PageFrontmatter } from '../types/index.js' /** * Resolve page route meta + * + * @internal */ export const resolvePageRouteMeta = ({ frontmatter, diff --git a/packages/core/src/page/resolvePageSlug.ts b/packages/core/src/page/resolvePageSlug.ts index a7329ffbaa..f9c3704571 100644 --- a/packages/core/src/page/resolvePageSlug.ts +++ b/packages/core/src/page/resolvePageSlug.ts @@ -4,6 +4,8 @@ const DATE_RE = /(\d{4}-\d{1,2}(-\d{1,2})?)-(.*)/ /** * Resolve page slug from filename + * + * @internal */ export const resolvePageSlug = ({ filePathRelative, diff --git a/packages/core/src/pluginApi/createHookQueue.ts b/packages/core/src/pluginApi/createHookQueue.ts index 09331eee3e..9fb70cc9ad 100644 --- a/packages/core/src/pluginApi/createHookQueue.ts +++ b/packages/core/src/pluginApi/createHookQueue.ts @@ -10,6 +10,8 @@ const log = debug('vuepress:core/plugin-api') /** * Create hook queue for plugin system + * + * @internal */ export const createHookQueue = (name: T): HookQueue => { const items: HookItem[] = [] diff --git a/packages/core/src/pluginApi/createPluginApi.ts b/packages/core/src/pluginApi/createPluginApi.ts index 4f90cda435..18aa270ed7 100644 --- a/packages/core/src/pluginApi/createPluginApi.ts +++ b/packages/core/src/pluginApi/createPluginApi.ts @@ -2,6 +2,11 @@ import type { PluginApi } from '../types/index.js' import { createPluginApiHooks } from './createPluginApiHooks.js' import { createPluginApiRegisterHooks } from './createPluginApiRegisterHooks.js' +/** + * Create vuepress plugin api + * + * @internal + */ export const createPluginApi = (): PluginApi => { const plugins: PluginApi['plugins'] = [] const hooks = createPluginApiHooks() diff --git a/packages/core/src/pluginApi/createPluginApiHooks.ts b/packages/core/src/pluginApi/createPluginApiHooks.ts index 0cefc33067..d8369985bb 100644 --- a/packages/core/src/pluginApi/createPluginApiHooks.ts +++ b/packages/core/src/pluginApi/createPluginApiHooks.ts @@ -1,6 +1,11 @@ import type { PluginApi } from '../types/index.js' import { createHookQueue } from './createHookQueue.js' +/** + * Create hooks for plugin api + * + * @internal + */ export const createPluginApiHooks = (): PluginApi['hooks'] => ({ // life cycle hooks onInitialized: createHookQueue('onInitialized'), diff --git a/packages/core/src/pluginApi/createPluginApiRegisterHooks.ts b/packages/core/src/pluginApi/createPluginApiRegisterHooks.ts index 79ee344016..22c94c4a01 100644 --- a/packages/core/src/pluginApi/createPluginApiRegisterHooks.ts +++ b/packages/core/src/pluginApi/createPluginApiRegisterHooks.ts @@ -2,6 +2,11 @@ import type { PluginApi } from '../types/index.js' import { normalizeAliasDefineHook } from './normalizeAliasDefineHook.js' import { normalizeClientConfigFileHook } from './normalizeClientConfigFileHook.js' +/** + * Create registerHooks method for plugin api + * + * @internal + */ export const createPluginApiRegisterHooks = ( plugins: PluginApi['plugins'], diff --git a/packages/core/src/pluginApi/normalizeAliasDefineHook.ts b/packages/core/src/pluginApi/normalizeAliasDefineHook.ts index 6c3da1a21b..1b12ac361b 100644 --- a/packages/core/src/pluginApi/normalizeAliasDefineHook.ts +++ b/packages/core/src/pluginApi/normalizeAliasDefineHook.ts @@ -3,6 +3,8 @@ import type { AliasDefineHook } from '../types/index.js' /** * Normalize alias and define hook + * + * @internal */ export const normalizeAliasDefineHook = (hook: AliasDefineHook['exposed']): AliasDefineHook['normalized'] => diff --git a/packages/core/src/pluginApi/normalizeClientConfigFileHook.ts b/packages/core/src/pluginApi/normalizeClientConfigFileHook.ts index bba349702c..a6475852e4 100644 --- a/packages/core/src/pluginApi/normalizeClientConfigFileHook.ts +++ b/packages/core/src/pluginApi/normalizeClientConfigFileHook.ts @@ -4,6 +4,8 @@ import type { ClientConfigFileHook } from '../types/index.js' /** * Normalize hook for client config file + * + * @internal */ export const normalizeClientConfigFileHook = (hook: ClientConfigFileHook['exposed']): ClientConfigFileHook['normalized'] => diff --git a/packages/core/src/types/app/app.ts b/packages/core/src/types/app/app.ts index 3bab071811..9a4c1cdb99 100644 --- a/packages/core/src/types/app/app.ts +++ b/packages/core/src/types/app/app.ts @@ -8,49 +8,39 @@ import type { AppOptions } from './options.js' import type { AppDir, AppEnv, AppWriteTemp } from './utils.js' /** - * Vuepress app + * App base properties, will be available after creation, even before initialization */ -export interface App { +export interface AppPropertiesBase { /** - * Directory utils + * Directory utils. */ dir: AppDir /** - * Environment flags + * Environment flags. */ env: AppEnv /** - * Options that filled all optional fields with a default value + * Options that filled all optional fields with a default value. */ options: AppOptions /** - * Plugin system + * Plugin system. */ pluginApi: PluginApi /** - * Site data, which will be used in client side + * Site data, which will be used in client side. */ siteData: SiteData /** - * Version of vuepress core + * Version of vuepress core. */ version: string - /** - * Write temp file - */ - writeTemp: AppWriteTemp - - /** - * Use a plugin - */ - use: (plugin: Plugin) => this - /** * Initialize app. * @@ -66,6 +56,23 @@ export interface App { */ prepare: () => Promise + /** + * Use a plugin. + * + * Should be called before `app.init()`. + */ + use: (plugin: Plugin) => this + + /** + * Util to write temp file + */ + writeTemp: AppWriteTemp +} + +/** + * App initialized properties, will only be available after initialization + */ +export interface AppPropertiesInitialized { /** * Markdown-it instance. * @@ -88,6 +95,11 @@ export interface App { pagesMap: Record } +/** + * Vuepress app instance + */ +export type App = AppPropertiesBase & AppPropertiesInitialized + /** * Vuepress dev app */ diff --git a/packages/core/src/types/app/options.ts b/packages/core/src/types/app/options.ts index 4cbcabbc37..74a11478f6 100644 --- a/packages/core/src/types/app/options.ts +++ b/packages/core/src/types/app/options.ts @@ -9,18 +9,98 @@ import type { Theme } from '../theme.js' * Vuepress app common config that shared between dev and build */ export interface AppConfigCommon extends Partial { + /** + * Source directory of the markdown files. + * + * Vuepress will load markdown files from this directory. + * + * @required + */ source: string + + /** + * Destination directory of the output files. + * + * Vuepress will output the static site files to this directory. + * + * @default `${source}/.vuepress/dist` + */ dest?: string + + /** + * Temp files directory. + * + * Vuepress will write temp files to this directory. + * + * @default `${source}/.vuepress/.temp` + */ temp?: string + + /** + * Cache files directory. + * + * Vuepress will write cache files to this directory. + * + * @default `${source}/.vuepress/.cache` + */ cache?: string + + /** + * Public files directory. + * + * Vuepress will copy the files from public directory to the output directory. + * + * @default `${source}/.vuepress/public` + */ public?: string + /** + * Whether to enable debug mode + * + * @default false + */ debug?: boolean + + /** + * Markdown options + * + * @default {} + */ markdown?: MarkdownOptions + + /** + * 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 bundler + * + * @required + */ bundler: Bundler + + /** + * Vuepress theme + * + * @required + */ theme: Theme + + /** + * Vuepress plugins + * + * @default [] + */ plugins?: PluginConfig } @@ -87,17 +167,21 @@ export interface AppConfigBuild { /** * Specify the HTML template renderer to be used for build * - * @default templateRenderer from '@vuepress/utils' + * @default `import { templateRenderer } from '@vuepress/utils'` */ templateBuildRenderer?: TemplateRenderer } /** - * Vuepress app config + * Vuepress app user config. + * + * It would be provided by user, typically via a config file. */ export type AppConfig = AppConfigBuild & AppConfigCommon & AppConfigDev /** - * Vuepress app options + * Vuepress app options that resolved from user config. + * + * It fills all optional fields with a default value. */ export type AppOptions = Required diff --git a/packages/core/src/types/app/utils.ts b/packages/core/src/types/app/utils.ts index 0e70eb5508..fdfb3fd4a7 100644 --- a/packages/core/src/types/app/utils.ts +++ b/packages/core/src/types/app/utils.ts @@ -7,11 +7,34 @@ export type AppDirFunction = (...args: string[]) => string * Directory utils */ export interface AppDir { + /** + * Resolve file path in cache directory + */ cache: AppDirFunction + + /** + * Resolve file path in temp directory + */ temp: AppDirFunction + + /** + * Resolve file path in source directory + */ source: AppDirFunction + + /** + * Resolve file path in dest directory + */ dest: AppDirFunction + + /** + * Resolve file path in public directory + */ public: AppDirFunction + + /** + * Resolve file path in client directory + */ client: AppDirFunction } diff --git a/packages/core/src/types/bundler.ts b/packages/core/src/types/bundler.ts index cd3b8dc2a7..0f8e64fa78 100644 --- a/packages/core/src/types/bundler.ts +++ b/packages/core/src/types/bundler.ts @@ -8,8 +8,19 @@ import type { App } from './app/index.js' * - build: bundle assets for deployment */ export interface Bundler { + /** + * Name of the bundler + */ name: string + + /** + * Method to run vuepress app in dev mode, starting dev server + */ dev: (app: App) => Promise<() => Promise> + + /** + * Method to run vuepress app in build mode, generating static pages and assets + */ build: (app: App) => Promise } diff --git a/packages/core/src/types/plugin.ts b/packages/core/src/types/plugin.ts index ae52a6b334..99e65f7517 100644 --- a/packages/core/src/types/plugin.ts +++ b/packages/core/src/types/plugin.ts @@ -27,10 +27,14 @@ export type PluginFunction = ( * Vuepress plugin object */ export interface PluginObject extends Partial { - // plugin name + /** + * Name of the plugin + */ name: string - // allow use a plugin multiple times or not + /** + * Allow the plugin to be used multiple times or not + */ multiple?: boolean } diff --git a/packages/core/tests/app/resolveAppEnv.spec.ts b/packages/core/tests/app/resolveAppEnv.spec.ts index 6ba685baa6..3c2ad10b83 100644 --- a/packages/core/tests/app/resolveAppEnv.spec.ts +++ b/packages/core/tests/app/resolveAppEnv.spec.ts @@ -1,65 +1,39 @@ -import { describe, expect, it } from 'vitest' +import { expect, it } from 'vitest' import type { Bundler } from '../../src/index.js' import { resolveAppEnv, resolveAppOptions } from '../../src/index.js' -const TEST_CASES: [ - Parameters, - ReturnType, -][] = [ - [ - [ - resolveAppOptions({ - source: '/foo', - theme: { name: 'test' }, - bundler: {} as Bundler, - }), - false, - ], - { +const TEST_CASES = [ + { + name: 'should resolve app env correctly without debug flag', + options: resolveAppOptions({ + source: '/foo', + theme: { name: 'test' }, + bundler: {} as Bundler, + }), + expected: { isBuild: false, - isDev: true, + isDev: false, isDebug: false, }, - ], - [ - [ - resolveAppOptions({ - source: '/foo', - theme: { name: 'test' }, - bundler: {} as Bundler, - debug: true, - }), - false, - ], - { + }, + { + name: 'should resolve app env correctly with debug flag', + options: resolveAppOptions({ + source: '/foo', + theme: { name: 'test' }, + bundler: {} as Bundler, + debug: true, + }), + expected: { isBuild: false, - isDev: true, - isDebug: true, - }, - ], - [ - [ - resolveAppOptions({ - source: '/foo', - theme: { name: 'test' }, - bundler: {} as Bundler, - }), - true, - ], - { - isBuild: true, isDev: false, - isDebug: false, + isDebug: true, }, - ], + }, ] -describe('core > app > resolveAppEnv', () => { - describe('should create app env correctly', () => { - TEST_CASES.forEach(([params, expected], i) => { - it(`case ${i}`, () => { - expect(resolveAppEnv(...params)).toEqual(expected) - }) - }) +TEST_CASES.forEach(({ name, options, expected }) => { + it(name, () => { + expect(resolveAppEnv(options)).toEqual(expected) }) }) diff --git a/packages/core/tests/app/resolveAppOptions.spec.ts b/packages/core/tests/app/resolveAppOptions.spec.ts index 442737587d..29b2d07d2d 100644 --- a/packages/core/tests/app/resolveAppOptions.spec.ts +++ b/packages/core/tests/app/resolveAppOptions.spec.ts @@ -1,49 +1,47 @@ import { path, templateRenderer } from '@vuepress/utils' -import { describe, expect, it } from 'vitest' +import { expect, it } from 'vitest' import type { Bundler } from '../../src/index.js' import { resolveAppOptions } from '../../src/index.js' -describe('core > app > resolveAppOptions', () => { - it('should create app options with default values', () => { - const source = '/foo' +it('should create app options with default values', () => { + const source = '/foo' - expect( - resolveAppOptions({ - source, - theme: { name: 'theme' }, - bundler: { name: 'bundler' } as Bundler, - }), - ).toEqual({ - base: '/', - lang: 'en-US', - title: '', - description: '', - head: [], - locales: {}, - theme: { name: 'theme' }, - bundler: { name: 'bundler' }, + expect( + resolveAppOptions({ source, - dest: path.resolve(source, '.vuepress/dist'), - temp: path.resolve(source, '.vuepress/.temp'), - cache: path.resolve(source, '.vuepress/.cache'), - public: path.resolve(source, '.vuepress/public'), - debug: false, - host: '0.0.0.0', - port: 8080, - open: false, - pagePatterns: ['**/*.md', '!.vuepress', '!node_modules'], - permalinkPattern: null, - templateDev: path.normalize( - require.resolve('@vuepress/client/templates/dev.html'), - ), - templateBuild: path.normalize( - require.resolve('@vuepress/client/templates/build.html'), - ), - templateBuildRenderer: templateRenderer, - shouldPreload: true, - shouldPrefetch: true, - markdown: {}, - plugins: [], - }) + theme: { name: 'theme' }, + bundler: { name: 'bundler' } as Bundler, + }), + ).toEqual({ + base: '/', + lang: 'en-US', + title: '', + description: '', + head: [], + locales: {}, + theme: { name: 'theme' }, + bundler: { name: 'bundler' }, + source, + dest: path.resolve(source, '.vuepress/dist'), + temp: path.resolve(source, '.vuepress/.temp'), + cache: path.resolve(source, '.vuepress/.cache'), + public: path.resolve(source, '.vuepress/public'), + debug: false, + host: '0.0.0.0', + port: 8080, + open: false, + pagePatterns: ['**/*.md', '!.vuepress', '!node_modules'], + permalinkPattern: null, + templateDev: path.normalize( + require.resolve('@vuepress/client/templates/dev.html'), + ), + templateBuild: path.normalize( + require.resolve('@vuepress/client/templates/build.html'), + ), + templateBuildRenderer: templateRenderer, + shouldPreload: true, + shouldPrefetch: true, + markdown: {}, + plugins: [], }) }) diff --git a/packages/core/tests/app/resolveAppPages.spec.ts b/packages/core/tests/app/resolveAppPages.spec.ts index a0366c1ab3..3203a8427f 100644 --- a/packages/core/tests/app/resolveAppPages.spec.ts +++ b/packages/core/tests/app/resolveAppPages.spec.ts @@ -1,93 +1,91 @@ import { createMarkdown } from '@vuepress/markdown' import { path } from '@vuepress/utils' -import { describe, expect, it } from 'vitest' +import { expect, it } from 'vitest' import type { Bundler } from '../../src/index.js' import { createBaseApp, resolveAppPages } from '../../src/index.js' -describe('core > app > resolveAppPages', () => { - it('should create two pages with default 404 page', async () => { - const app = createBaseApp({ - source: path.resolve(__dirname, '../__fixtures__/pages'), - theme: { name: 'test' }, - bundler: {} as Bundler, - }) - app.markdown = createMarkdown() +it('should create two pages with default 404 page', async () => { + const app = createBaseApp({ + source: path.resolve(__dirname, '../__fixtures__/pages'), + theme: { name: 'test' }, + bundler: {} as Bundler, + }) + 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 { 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') + expect(pages).toHaveLength(3) + expect(fooPage?.filePathRelative).toEqual('foo.md') + expect(barPage?.filePathRelative).toEqual('bar.md') + expect(notFoundPage?.filePathRelative).toBeNull() + expect(notFoundPage?.frontmatter.layout).toEqual('NotFound') +}) - expect(pages).toHaveLength(3) - expect(fooPage?.filePathRelative).toEqual('foo.md') - expect(barPage?.filePathRelative).toEqual('bar.md') - expect(notFoundPage?.filePathRelative).toBeNull() - expect(notFoundPage?.frontmatter.layout).toEqual('NotFound') +it('should create two pages with custom 404 page', async () => { + const app = createBaseApp({ + source: path.resolve(__dirname, '../__fixtures__/pages-with-404'), + theme: { name: 'test' }, + bundler: {} as Bundler, }) + app.markdown = createMarkdown() - it('should create two pages with custom 404 page', async () => { - const app = createBaseApp({ - source: path.resolve(__dirname, '../__fixtures__/pages-with-404'), - theme: { name: 'test' }, - bundler: {} as Bundler, - }) - 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 { 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') + expect(pages).toHaveLength(3) + expect(fooPage?.filePathRelative).toEqual('foo.md') + expect(barPage?.filePathRelative).toEqual('bar.md') + expect(notFoundPage?.filePathRelative).toEqual('404.md') +}) - expect(pages).toHaveLength(3) - expect(fooPage?.filePathRelative).toEqual('foo.md') - expect(barPage?.filePathRelative).toEqual('bar.md') - expect(notFoundPage?.filePathRelative).toEqual('404.md') +it('should process extendsPageOptions hook correctly', async () => { + const app = createBaseApp({ + source: path.resolve(__dirname, '../__fixtures__/pages-with-404'), + theme: { name: 'test' }, + bundler: {} as Bundler, }) - it('should process extendsPageOptions hook correctly', async () => { - const app = createBaseApp({ - source: path.resolve(__dirname, '../__fixtures__/pages-with-404'), - theme: { name: 'test' }, - bundler: {} as Bundler, - }) - - app.use({ - name: 'foo', - extendsPageOptions: (pageOptions) => { - if (!pageOptions.frontmatter) pageOptions.frontmatter = {} - pageOptions.frontmatter.foo = 'bar' - }, - }) - app.pluginApi.registerHooks() - app.markdown = createMarkdown() + app.use({ + name: 'foo', + extendsPageOptions: (pageOptions) => { + if (!pageOptions.frontmatter) pageOptions.frontmatter = {} + pageOptions.frontmatter.foo = 'bar' + }, + }) + app.pluginApi.registerHooks() + app.markdown = createMarkdown() - const { pages } = await resolveAppPages(app) + const { pages } = await resolveAppPages(app) - pages.forEach((page) => { - expect(page.frontmatter.foo).toBe('bar') - }) + pages.forEach((page) => { + expect(page.frontmatter.foo).toBe('bar') }) +}) - it('should process extendsPage hook correctly', async () => { - const app = createBaseApp({ - source: path.resolve(__dirname, '../__fixtures__/pages-with-404'), - theme: { name: 'test' }, - bundler: {} as Bundler, - }) +it('should process extendsPage hook correctly', async () => { + const app = createBaseApp({ + source: path.resolve(__dirname, '../__fixtures__/pages-with-404'), + theme: { name: 'test' }, + bundler: {} as Bundler, + }) - app.use({ - name: 'foo', - extendsPage: (page) => { - page.frontmatter.foo = 'baz' - }, - }) - app.pluginApi.registerHooks() - app.markdown = createMarkdown() + app.use({ + name: 'foo', + extendsPage: (page) => { + page.frontmatter.foo = 'baz' + }, + }) + app.pluginApi.registerHooks() + app.markdown = createMarkdown() - const { pages } = await resolveAppPages(app) + const { pages } = await resolveAppPages(app) - pages.forEach((page) => { - expect(page.frontmatter.foo).toBe('baz') - }) + pages.forEach((page) => { + expect(page.frontmatter.foo).toBe('baz') }) }) diff --git a/packages/core/tests/app/resolvePluginObject.spec.ts b/packages/core/tests/app/resolvePluginObject.spec.ts index d359120252..a9daeb0e67 100644 --- a/packages/core/tests/app/resolvePluginObject.spec.ts +++ b/packages/core/tests/app/resolvePluginObject.spec.ts @@ -1,5 +1,5 @@ import { path } from '@vuepress/utils' -import { describe, expect, it, vi } from 'vitest' +import { expect, it, vi } from 'vitest' import type { Bundler, PluginFunction, PluginObject } from '../../src/index.js' import { createBaseApp, resolvePluginObject } from '../../src/index.js' @@ -9,25 +9,23 @@ const app = createBaseApp({ bundler: {} as Bundler, }) -describe('core > app > resolvePluginObject', () => { - it('should work with plugin object', () => { - const pluginObject: PluginObject = { - name: 'plugin-object', - } +it('should work with plugin object', () => { + const pluginObject: PluginObject = { + name: 'plugin-object', + } - const result = resolvePluginObject(app, pluginObject) - expect(result.name).toEqual('plugin-object') - }) + const result = resolvePluginObject(app, pluginObject) + expect(result.name).toEqual('plugin-object') +}) - it('should work with plugin function', () => { - const pluginFunction: PluginFunction = vi.fn(() => ({ - name: 'plugin-function', - })) +it('should work with plugin function', () => { + const pluginFunction: PluginFunction = vi.fn(() => ({ + name: 'plugin-function', + })) - const result = resolvePluginObject(app, pluginFunction) + const result = resolvePluginObject(app, pluginFunction) - expect(pluginFunction).toHaveBeenCalledTimes(1) - expect(pluginFunction).toHaveBeenCalledWith(app) - expect(result.name).toEqual('plugin-function') - }) + expect(pluginFunction).toHaveBeenCalledTimes(1) + expect(pluginFunction).toHaveBeenCalledWith(app) + expect(result.name).toEqual('plugin-function') }) diff --git a/packages/core/tests/app/resolveThemeInfo.spec.ts b/packages/core/tests/app/resolveThemeInfo.spec.ts index 631deb2e6a..453bae2c90 100644 --- a/packages/core/tests/app/resolveThemeInfo.spec.ts +++ b/packages/core/tests/app/resolveThemeInfo.spec.ts @@ -23,76 +23,74 @@ const getThemePlugin = async ( return typeof theme === 'function' ? theme(app) : theme } -describe('core > app > resolveThemeInfo', () => { - describe('plugins', () => { - describe('should resolve theme info without plugins correctly', () => { - THEME_ENTRY_TYPES.forEach((item) => { - it(item, async () => { - const themePath = fixtures(`themes/${item}-empty.js`) - const app = await createTestApp(themePath) - expect(resolveThemeInfo(app, app.options.theme).plugins).toEqual([ - await getThemePlugin(themePath, app), - ]) - }) +describe('plugins', () => { + describe('should resolve theme info without plugins correctly', () => { + THEME_ENTRY_TYPES.forEach((item) => { + it(item, async () => { + const themePath = fixtures(`themes/${item}-empty.js`) + const app = await createTestApp(themePath) + expect(resolveThemeInfo(app, app.options.theme).plugins).toEqual([ + await getThemePlugin(themePath, app), + ]) }) }) + }) - describe('should resolve theme info with plugins correctly', () => { - THEME_ENTRY_TYPES.forEach((item) => { - it(item, async () => { - const themePath = fixtures(`themes/${item}.js`) - const app = await createTestApp(themePath) - expect(resolveThemeInfo(app, app.options.theme).plugins).toEqual([ - await importFileDefault(fixtures('plugins/obj.js')), - await getThemePlugin(themePath, app), - ]) - }) + describe('should resolve theme info with plugins correctly', () => { + THEME_ENTRY_TYPES.forEach((item) => { + it(item, async () => { + const themePath = fixtures(`themes/${item}.js`) + const app = await createTestApp(themePath) + expect(resolveThemeInfo(app, app.options.theme).plugins).toEqual([ + await importFileDefault(fixtures('plugins/obj.js')), + await getThemePlugin(themePath, app), + ]) }) }) }) +}) - describe('extends', () => { - describe('should resolve theme info with parent theme correctly', () => { - THEME_ENTRY_TYPES.forEach((item) => { - it(item, async () => { - const themePath = fixtures(`themes/${item}-extends-parent.js`) - const parentThemePath = fixtures(`themes/${item}.js`) - const app = await createTestApp(themePath) +describe('extends', () => { + describe('should resolve theme info with parent theme correctly', () => { + THEME_ENTRY_TYPES.forEach((item) => { + it(item, async () => { + const themePath = fixtures(`themes/${item}-extends-parent.js`) + const parentThemePath = fixtures(`themes/${item}.js`) + const app = await createTestApp(themePath) - expect(resolveThemeInfo(app, app.options.theme)).toEqual({ - plugins: [ - await importFileDefault(fixtures('plugins/obj.js')), - await getThemePlugin(parentThemePath, app), - await importFileDefault(fixtures('plugins/obj-foo.js')), - await getThemePlugin(themePath, app), - ], - templateBuild: `theme-${item}-extends-parent-template-build`, - templateDev: `theme-${item}-template-dev`, - }) + expect(resolveThemeInfo(app, app.options.theme)).toEqual({ + plugins: [ + await importFileDefault(fixtures('plugins/obj.js')), + await getThemePlugin(parentThemePath, app), + await importFileDefault(fixtures('plugins/obj-foo.js')), + await getThemePlugin(themePath, app), + ], + templateBuild: `theme-${item}-extends-parent-template-build`, + templateDev: `theme-${item}-template-dev`, }) }) }) + }) - describe('should resolve theme info with grandparent theme correctly', () => { - THEME_ENTRY_TYPES.forEach((item) => { - it(item, async () => { - const themePath = fixtures(`themes/${item}-extends-grandparent.js`) - const parentThemePath = fixtures(`themes/${item}-extends-parent.js`) - const grandparentThemePath = fixtures(`themes/${item}.js`) - const app = await createTestApp(themePath) + describe('should resolve theme info with grandparent theme correctly', () => { + THEME_ENTRY_TYPES.forEach((item) => { + it(item, async () => { + const themePath = fixtures(`themes/${item}-extends-grandparent.js`) + const parentThemePath = fixtures(`themes/${item}-extends-parent.js`) + const grandparentThemePath = fixtures(`themes/${item}.js`) + const app = await createTestApp(themePath) - expect(resolveThemeInfo(app, app.options.theme)).toEqual({ - plugins: [ - await importFileDefault(fixtures('plugins/obj.js')), - await getThemePlugin(grandparentThemePath, app), - await importFileDefault(fixtures('plugins/obj-foo.js')), - await getThemePlugin(parentThemePath, app), - await importFileDefault(fixtures('plugins/obj-bar.js')), - await getThemePlugin(themePath, app), - ], - templateBuild: `theme-${item}-extends-parent-template-build`, - templateDev: `theme-${item}-extends-grandparent-template-dev`, - }) + expect(resolveThemeInfo(app, app.options.theme)).toEqual({ + plugins: [ + await importFileDefault(fixtures('plugins/obj.js')), + await getThemePlugin(grandparentThemePath, app), + await importFileDefault(fixtures('plugins/obj-foo.js')), + await getThemePlugin(parentThemePath, app), + await importFileDefault(fixtures('plugins/obj-bar.js')), + await getThemePlugin(themePath, app), + ], + templateBuild: `theme-${item}-extends-parent-template-build`, + templateDev: `theme-${item}-extends-grandparent-template-dev`, }) }) }) diff --git a/packages/markdown/tests/markdown.spec.ts b/packages/markdown/tests/markdown.spec.ts index ec5ddfffbe..0cf9097b44 100644 --- a/packages/markdown/tests/markdown.spec.ts +++ b/packages/markdown/tests/markdown.spec.ts @@ -1,26 +1,24 @@ -import { describe, expect, it } from 'vitest' +import { expect, it } from 'vitest' import { createMarkdown } from '../src/index.js' -describe('@vuepress/markdown > markdown', () => { - const md = createMarkdown() +const md = createMarkdown() - it('should render anchors', () => { - const rendered = md.render(`\ +it('should render anchors', () => { + const rendered = md.render(`\ # h1 ## h2 ### h3 `) - expect(rendered).toEqual( - [ - '

h1

', - '

h2

', - '

h3

', - ].join('\n') + '\n', - ) - }) + expect(rendered).toEqual( + [ + '

h1

', + '

h2

', + '

h3

', + ].join('\n') + '\n', + ) +}) - it('should parse emoji', () => { - const rendered = md.render(':smile:') - expect(rendered).toBe('

😄

\n') - }) +it('should parse emoji', () => { + const rendered = md.render(':smile:') + expect(rendered).toBe('

😄

\n') }) diff --git a/packages/markdown/tests/plugins/__snapshots__/importCodePlugin.spec.ts.snap b/packages/markdown/tests/plugins/__snapshots__/importCodePlugin.spec.ts.snap index afce065776..315d75627c 100644 --- a/packages/markdown/tests/plugins/__snapshots__/importCodePlugin.spec.ts.snap +++ b/packages/markdown/tests/plugins/__snapshots__/importCodePlugin.spec.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`@vuepress/markdown > plugins > importCodePlugin > compatibility with otherPlugin > should preserve the things after code as fence info 1`] = ` +exports[`compatibility with otherPlugin > should preserve the things after code as fence info 1`] = ` "
const msg = 'hello from js'
 
 console.log(msg)
@@ -10,7 +10,7 @@ console.log('foo bar')
 "
 `;
 
-exports[`@vuepress/markdown > plugins > importCodePlugin > compatibility with otherPlugin > should preserve the things after code as fence info 2`] = `
+exports[`compatibility with otherPlugin > should preserve the things after code as fence info 2`] = `
 "
# msg
 
 hello from md
diff --git a/packages/markdown/tests/plugins/__snapshots__/vPrePlugin.spec.ts.snap b/packages/markdown/tests/plugins/__snapshots__/vPrePlugin.spec.ts.snap
index 6cb750f95f..a06960e9b8 100644
--- a/packages/markdown/tests/plugins/__snapshots__/vPrePlugin.spec.ts.snap
+++ b/packages/markdown/tests/plugins/__snapshots__/vPrePlugin.spec.ts.snap
@@ -1,6 +1,6 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
-exports[`@vuepress/markdown > plugins > vPrePlugin > :v-pre / :no-v-pre > should work if \`block\` is disabled 1`] = `
+exports[`:v-pre / :no-v-pre > should work if \`block\` is disabled 1`] = `
 "
const a = 1
 
const a = 1
@@ -18,7 +18,7 @@ exports[`@vuepress/markdown > plugins > vPrePlugin > :v-pre / :no-v-pre > should
 "
 `;
 
-exports[`@vuepress/markdown > plugins > vPrePlugin > :v-pre / :no-v-pre > should work if \`block\` is enabled by default 1`] = `
+exports[`:v-pre / :no-v-pre > should work if \`block\` is enabled by default 1`] = `
 "
const a = 1
 
const a = 1
@@ -36,35 +36,35 @@ exports[`@vuepress/markdown > plugins > vPrePlugin > :v-pre / :no-v-pre > should
 "
 `;
 
-exports[`@vuepress/markdown > plugins > vPrePlugin > plugin options > should disable \`block\` 1`] = `
+exports[`plugin options > should disable \`block\` 1`] = `
 "
const a = 1
 

inline

" `; -exports[`@vuepress/markdown > plugins > vPrePlugin > plugin options > should disable \`block\` and \`inline\` 1`] = ` +exports[`plugin options > should disable \`block\` and \`inline\` 1`] = ` "
const a = 1
 

inline

" `; -exports[`@vuepress/markdown > plugins > vPrePlugin > plugin options > should disable \`inline\` 1`] = ` +exports[`plugin options > should disable \`inline\` 1`] = ` "
const a = 1
 

inline

" `; -exports[`@vuepress/markdown > plugins > vPrePlugin > plugin options > should process code with default options 1`] = ` +exports[`plugin options > should process code with default options 1`] = ` "
const a = 1
 

inline

" `; -exports[`@vuepress/markdown > plugins > vPrePlugin > syntax highlighting > should work highlighted code is wrapped with \`
\` 1`] = `
+exports[`syntax highlighting > should work highlighted code is wrapped with \`
\` 1`] = `
 "
highlighted code: const a = 1
 , lang: js:v-pre
highlighted code: const a = 1
@@ -82,7 +82,7 @@ exports[`@vuepress/markdown > plugins > vPrePlugin > syntax highlighting > shoul
 "
 `;
 
-exports[`@vuepress/markdown > plugins > vPrePlugin > syntax highlighting > should work if highlighted code is not wrapped with \`
\` 1`] = `
+exports[`syntax highlighting > should work if highlighted code is not wrapped with \`
\` 1`] = `
 "
highlighted code: const a = 1
 , lang: js:v-pre
highlighted code: const a = 1
diff --git a/packages/markdown/tests/plugins/assetsPlugin.spec.ts b/packages/markdown/tests/plugins/assetsPlugin.spec.ts
index 34dca2e41d..e2780c2e51 100644
--- a/packages/markdown/tests/plugins/assetsPlugin.spec.ts
+++ b/packages/markdown/tests/plugins/assetsPlugin.spec.ts
@@ -3,39 +3,211 @@ import { describe, expect, it } from 'vitest'
 import type { MarkdownEnv } from '../../src/index.js'
 import { assetsPlugin } from '../../src/index.js'
 
-describe('@vuepress/markdown > plugins > assetsPlugin', () => {
-  describe('markdown image syntax', () => {
+describe('markdown image syntax', () => {
+  const source = [
+    // relative paths
+    '![foo](./foo.png)',
+    '![foo2](../sub/foo.png)',
+    '![foo-bar](./foo/bar.png)',
+    '![foo-bar2](../sub/foo/bar.png)',
+    '![baz](../baz.png)',
+    '![out](../../out.png)',
+    '![汉字](./汉字.png)',
+    '![100%](./100%.png)',
+    // absolute paths
+    '![absolute](/absolute.png)',
+    '![absolute-foo](/foo/absolute.png)',
+    // no-prefix paths
+    '![no-prefix](no-prefix.png)',
+    '![no-prefix-foo](foo/no-prefix.png)',
+    '![alias](@alias/foo.png)',
+    '![汉字](@alias/汉字.png)',
+    '![100%](@alias/100%.png)',
+    '![~alias](~@alias/foo.png)',
+    '![~汉字](~@alias/汉字.png)',
+    '![~100%](~@alias/100%.png)',
+    // keep as is
+    '![url](http://foobar.com/icon.png)',
+    '![empty]()',
+    // invalid paths
+    '![invalid](.../invalid.png)',
+    '![汉字](.../汉字.png)',
+    '![100%](.../100%.png)',
+    // data uri
+    '![data-uri](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wr4H/wAAAABJRU5ErkJggg==)',
+  ]
+
+  const TEST_CASES: {
+    description: string
+    md: MarkdownIt
+    env: MarkdownEnv
+    expected: string[]
+  }[] = [
+    {
+      description: 'should handle assets link with default options',
+      md: MarkdownIt().use(assetsPlugin),
+      env: {
+        base: '/base/',
+      },
+      expected: [
+        // relative paths
+        'foo',
+        'foo2',
+        'foo-bar',
+        'foo-bar2',
+        'baz',
+        'out',
+        '汉字',
+        '100%',
+        // absolute paths
+        'absolute',
+        'absolute-foo',
+        // no-prefix paths
+        'no-prefix',
+        'no-prefix-foo',
+        'alias',
+        '汉字',
+        '100%',
+        '~alias',
+        '~汉字',
+        '~100%',
+        // keep as is
+        'url',
+        'empty',
+        // invalid paths
+        'invalid',
+        '汉字',
+        '100%',
+        // data uri
+        'data-uri',
+      ],
+    },
+    {
+      description: 'should respect `absolutePathPrependBase` option',
+      md: MarkdownIt().use(assetsPlugin, {
+        absolutePathPrependBase: true,
+      }),
+      env: {
+        base: '/base/',
+      },
+      expected: [
+        // relative paths
+        'foo',
+        'foo2',
+        'foo-bar',
+        'foo-bar2',
+        'baz',
+        'out',
+        '汉字',
+        '100%',
+        // absolute paths
+        'absolute',
+        'absolute-foo',
+        // no-prefix paths
+        'no-prefix',
+        'no-prefix-foo',
+        'alias',
+        '汉字',
+        '100%',
+        '~alias',
+        '~汉字',
+        '~100%',
+        // keep as is
+        'url',
+        'empty',
+        // invalid paths
+        'invalid',
+        '汉字',
+        '100%',
+        // data uri
+        'data-uri',
+      ],
+    },
+  ]
+
+  TEST_CASES.forEach(({ description, md, env, expected }) => {
+    it(description, () => {
+      expect(md.render(source.join('\n\n'), env)).toEqual(
+        `${expected.map((item) => `

${item}

`).join('\n')}\n`, + ) + }) + }) +}) + +describe('html tag', () => { + describe('single-line', () => { const source = [ + /* src */ // relative paths - '![foo](./foo.png)', - '![foo2](../sub/foo.png)', - '![foo-bar](./foo/bar.png)', - '![foo-bar2](../sub/foo/bar.png)', - '![baz](../baz.png)', - '![out](../../out.png)', - '![汉字](./汉字.png)', - '![100%](./100%.png)', + '', + '', + '', + '', + '', + '', + '', + '', + 'attrs', + // aliases + '', + '', + '', + 'attrs', + // webpack legacy aliases + '', + '', + '', + 'attrs', // absolute paths - '![absolute](/absolute.png)', - '![absolute-foo](/foo/absolute.png)', + '', + '', // no-prefix paths - '![no-prefix](no-prefix.png)', - '![no-prefix-foo](foo/no-prefix.png)', - '![alias](@alias/foo.png)', - '![汉字](@alias/汉字.png)', - '![100%](@alias/100%.png)', - '![~alias](~@alias/foo.png)', - '![~汉字](~@alias/汉字.png)', - '![~100%](~@alias/100%.png)', + '', + '', + 'attrs', + // keep as is + '', + '', + // invalid paths + '', + '', + '', + 'attrs', + + /* srcset */ + // relative paths + '', + '', + 'attrs', + // aliases + '', + 'attrs', + // webpack legacy aliases + '', + 'attrs', // keep as is - '![url](http://foobar.com/icon.png)', - '![empty]()', + '', + '', + 'attrs', // invalid paths - '![invalid](.../invalid.png)', - '![汉字](.../汉字.png)', - '![100%](.../100%.png)', - // data uri - '![data-uri](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wr4H/wAAAABJRU5ErkJggg==)', + '', + 'attrs', + // invalid srcset + '', + + /* both */ + // relative paths + '', + '', + 'attrs', + // aliases + 'attrs', + 'attrs', + // keep as is + 'attrs', + + /* data uri */ + '', ] const TEST_CASES: { @@ -46,516 +218,336 @@ describe('@vuepress/markdown > plugins > assetsPlugin', () => { }[] = [ { description: 'should handle assets link with default options', - md: MarkdownIt().use(assetsPlugin), + md: MarkdownIt({ html: true }).use(assetsPlugin), env: { base: '/base/', }, expected: [ + /* src */ // relative paths - 'foo', - 'foo2', - 'foo-bar', - 'foo-bar2', - 'baz', - 'out', - '汉字', - '100%', + '', + '', + '', + '', + '', + '', + '', + '', + 'attrs', + // aliases + '', + '', + '', + 'attrs', + // webpack legacy aliases + '', + '', + '', + 'attrs', // absolute paths - 'absolute', - 'absolute-foo', + '', + '', // no-prefix paths - 'no-prefix', - 'no-prefix-foo', - 'alias', - '汉字', - '100%', - '~alias', - '~汉字', - '~100%', + '', + '', + 'attrs', // keep as is - 'url', - 'empty', + '', + '', // invalid paths - 'invalid', - '汉字', - '100%', - // data uri - 'data-uri', + '', + '', + '', + 'attrs', + + /* srcset */ + // relative paths + '', + '', + 'attrs', + // aliases + '', + 'attrs', + // webpack legacy aliases + '', + 'attrs', + // absolute paths and no-prefix paths + '', + // keep as is + '', + 'attrs', + // invalid paths + '', + 'attrs', + // invalid srcset + '', + + /* both */ + // relative paths + '', + '', + 'attrs', + // aliases + 'attrs', + 'attrs', + // absolute paths and no-prefix paths + 'attrs', + + /* data uri */ + '', ], }, { description: 'should respect `absolutePathPrependBase` option', - md: MarkdownIt().use(assetsPlugin, { + md: MarkdownIt({ html: true }).use(assetsPlugin, { absolutePathPrependBase: true, }), env: { base: '/base/', }, expected: [ + /* src */ // relative paths - 'foo', - 'foo2', - 'foo-bar', - 'foo-bar2', - 'baz', - 'out', - '汉字', - '100%', + '', + '', + '', + '', + '', + '', + '', + '', + 'attrs', + // aliases + '', + '', + '', + 'attrs', + // webpack legacy aliases + '', + '', + '', + 'attrs', // absolute paths - 'absolute', - 'absolute-foo', + '', + '', // no-prefix paths - 'no-prefix', - 'no-prefix-foo', - 'alias', - '汉字', - '100%', - '~alias', - '~汉字', - '~100%', + '', + '', + 'attrs', // keep as is - 'url', - 'empty', + '', + '', // invalid paths - 'invalid', - '汉字', - '100%', - // data uri - 'data-uri', + '', + '', + '', + 'attrs', + + /* srcset */ + // relative paths + '', + '', + 'attrs', + // aliases + '', + 'attrs', + // webpack legacy aliases + '', + 'attrs', + // absolute paths and no-prefix paths + '', + // keep as is + '', + 'attrs', + // invalid paths + '', + 'attrs', + // invalid srcset + '', + + /* both */ + // relative paths + '', + '', + 'attrs', + // aliases + 'attrs', + 'attrs', + // absolute paths and no-prefix paths + 'attrs', + + /* data uri */ + '', ], }, ] TEST_CASES.forEach(({ description, md, env, expected }) => { it(description, () => { + // block expect(md.render(source.join('\n\n'), env)).toEqual( - `${expected.map((item) => `

${item}

`).join('\n')}\n`, + expected.map((item) => item).join('\n'), ) - }) - }) - }) - describe('html tag', () => { - describe('single-line', () => { - const source = [ - /* src */ - // relative paths - '', - '', - '', - '', - '', - '', - '', - '', - 'attrs', - // aliases - '', - '', - '', - 'attrs', - // webpack legacy aliases - '', - '', - '', - 'attrs', - // absolute paths - '', - '', - // no-prefix paths - '', - '', - 'attrs', - // keep as is - '', - '', - // invalid paths - '', - '', - '', - 'attrs', + // block with leading white space + expect( + md.render(source.map((item) => ` ${item}`).join('\n\n'), env), + ).toEqual(expected.map((item) => ` ${item}`).join('\n')) + + // inline with prefix + expect( + md.render(source.map((item) => `foo${item}`).join('\n\n'), env), + ).toEqual(`${expected.map((item) => `

foo${item}

`).join('\n')}\n`) + + // inline with suffix + expect( + md.render(source.map((item) => `${item}foo`).join('\n\n'), env), + ).toEqual(`${expected.map((item) => `

${item}foo

`).join('\n')}\n`) + + // inline with line break + expect( + md.render( + source.map((item) => item.replace(' `

${item.replace('`) + .join('\n')}\n`, + ) - /* srcset */ - // relative paths - '', - '', - 'attrs', - // aliases - '', - 'attrs', - // webpack legacy aliases - '', - 'attrs', - // keep as is - '', - '', - 'attrs', - // invalid paths - '', - 'attrs', - // invalid srcset - '', + // wrapped item + expect( + md.render(source.map((item) => `

${item}

`).join('\n\n'), env), + ).toEqual(expected.map((item) => `

${item}

`).join('\n')) - /* both */ - // relative paths - '', - '', - 'attrs', - // aliases - 'attrs', - 'attrs', - // keep as is - 'attrs', - - /* data uri */ - '', - ] - - const TEST_CASES: { - description: string - md: MarkdownIt - env: MarkdownEnv - expected: string[] - }[] = [ - { - description: 'should handle assets link with default options', - md: MarkdownIt({ html: true }).use(assetsPlugin), - env: { - base: '/base/', - }, - expected: [ - /* src */ - // relative paths - '', - '', - '', - '', - '', - '', - '', - '', - 'attrs', - // aliases - '', - '', - '', - 'attrs', - // webpack legacy aliases - '', - '', - '', - 'attrs', - // absolute paths - '', - '', - // no-prefix paths - '', - '', - 'attrs', - // keep as is - '', - '', - // invalid paths - '', - '', - '', - 'attrs', - - /* srcset */ - // relative paths - '', - '', - 'attrs', - // aliases - '', - 'attrs', - // webpack legacy aliases - '', - 'attrs', - // absolute paths and no-prefix paths - '', - // keep as is - '', - 'attrs', - // invalid paths - '', - 'attrs', - // invalid srcset - '', - - /* both */ - // relative paths - '', - '', - 'attrs', - // aliases - 'attrs', - 'attrs', - // absolute paths and no-prefix paths - 'attrs', - - /* data uri */ - '', - ], - }, - { - description: 'should respect `absolutePathPrependBase` option', - md: MarkdownIt({ html: true }).use(assetsPlugin, { - absolutePathPrependBase: true, - }), - env: { - base: '/base/', - }, - expected: [ - /* src */ - // relative paths - '', - '', - '', - '', - '', - '', - '', - '', - 'attrs', - // aliases - '', - '', - '', - 'attrs', - // webpack legacy aliases - '', - '', - '', - 'attrs', - // absolute paths - '', - '', - // no-prefix paths - '', - '', - 'attrs', - // keep as is - '', - '', - // invalid paths - '', - '', - '', - 'attrs', - - /* srcset */ - // relative paths - '', - '', - 'attrs', - // aliases - '', - 'attrs', - // webpack legacy aliases - '', - 'attrs', - // absolute paths and no-prefix paths - '', - // keep as is - '', - 'attrs', - // invalid paths - '', - 'attrs', - // invalid srcset - '', - - /* both */ - // relative paths - '', - '', - 'attrs', - // aliases - 'attrs', - 'attrs', - // absolute paths and no-prefix paths - 'attrs', - - /* data uri */ - '', - ], - }, - ] - - TEST_CASES.forEach(({ description, md, env, expected }) => { - it(description, () => { - // block - expect(md.render(source.join('\n\n'), env)).toEqual( - expected.map((item) => item).join('\n'), - ) - - // block with leading white space - expect( - md.render(source.map((item) => ` ${item}`).join('\n\n'), env), - ).toEqual(expected.map((item) => ` ${item}`).join('\n')) - - // inline with prefix - expect( - md.render(source.map((item) => `foo${item}`).join('\n\n'), env), - ).toEqual( - `${expected.map((item) => `

foo${item}

`).join('\n')}\n`, - ) - - // inline with suffix - expect( - md.render(source.map((item) => `${item}foo`).join('\n\n'), env), - ).toEqual( - `${expected.map((item) => `

${item}foo

`).join('\n')}\n`, - ) - - // inline with line break - expect( - md.render( - source.map((item) => item.replace(' `

${item.replace('`) - .join('\n')}\n`, - ) - - // wrapped item - expect( - md.render(source.map((item) => `

${item}

`).join('\n\n'), env), - ).toEqual(expected.map((item) => `

${item}

`).join('\n')) - - // wrapped item with line break - expect( - md.render( - source - .map((item) => `

${item.replace('`) - .join('\n\n'), - env, - ), - ).toEqual( - expected + // wrapped item with line break + expect( + md.render( + source .map((item) => `

${item.replace('`) - .join('\n'), - ) - - // wrapped multiple items - expect( - md.render( - source.map((item) => `

${item}${item}

`).join('\n\n'), - env, - ), - ).toEqual(expected.map((item) => `

${item}${item}

`).join('\n')) - - // deeply wrapped multiple items - expect( - md.render( - source - .map((item) => `

\n\n${item}\n${item}\n\n

`) - .join('\n\n'), - env, - ), - ).toEqual( - expected + .join('\n\n'), + env, + ), + ).toEqual( + expected + .map((item) => `

${item.replace('`) + .join('\n'), + ) + + // wrapped multiple items + expect( + md.render( + source.map((item) => `

${item}${item}

`).join('\n\n'), + env, + ), + ).toEqual(expected.map((item) => `

${item}${item}

`).join('\n')) + + // deeply wrapped multiple items + expect( + md.render( + source .map((item) => `

\n\n${item}\n${item}\n\n

`) - .join('\n'), - ) - }) + .join('\n\n'), + env, + ), + ).toEqual( + expected + .map((item) => `

\n\n${item}\n${item}\n\n

`) + .join('\n'), + ) }) }) + }) - // multi-line `` tag will be wrapped with `

` tag - describe('multi-line', () => { - const source = [ - /* src */ - `attrs`, - `attrs`, - /* srcset */ - ``, - `attrs`, - /** both */ - ``, - `attrs`, - ] - - const TEST_CASES: { - description: string - md: MarkdownIt - env: MarkdownEnv - expected: string[] - }[] = [ - { - description: 'should handle assets link with default options', - md: MarkdownIt({ html: true }).use(assetsPlugin), - env: { - base: '/base/', - }, - expected: [ - /* src */ - '

attrs

', - '

attrs

', - - /* srcset */ - '

', - '

attrs

', - - /* both */ - '

', - '

attrs

', - ], + ] + + const TEST_CASES: { + description: string + md: MarkdownIt + env: MarkdownEnv + expected: string[] + }[] = [ + { + description: 'should handle assets link with default options', + md: MarkdownIt({ html: true }).use(assetsPlugin), + env: { + base: '/base/', }, - { - description: 'should respect `absolutePathPrependBase` option', - md: MarkdownIt({ html: true }).use(assetsPlugin, { - absolutePathPrependBase: true, - }), - env: { - base: '/base/', - }, - expected: [ - /* src */ - '

attrs

', - '

attrs

', - - /* srcset */ - '

', - '

attrs

', - - /* both */ - '

', - '

attrs

', - ], + expected: [ + /* src */ + '

attrs

', + '

attrs

', + + /* srcset */ + '

', + '

attrs

', + + /* both */ + '

', + '

attrs

', + ], + }, + { + description: 'should respect `absolutePathPrependBase` option', + md: MarkdownIt({ html: true }).use(assetsPlugin, { + absolutePathPrependBase: true, + }), + env: { + base: '/base/', }, - ] - - TEST_CASES.forEach(({ description, md, env, expected }) => { - it(description, () => { - // double quote - expect(md.render(source.join('\n\n'), env)).toEqual( - `${expected.map((item) => item).join('\n')}\n`, - ) - // single quote - expect( - md.render(source.join('\n\n').replace(/"/g, "'"), env), - ).toEqual( - `${expected.map((item) => item.replace(/"/g, "'")).join('\n')}\n`, - ) - }) + expected: [ + /* src */ + '

attrs

', + '

attrs

', + + /* srcset */ + '

', + '

attrs

', + + /* both */ + '

', + '

attrs

', + ], + }, + ] + + TEST_CASES.forEach(({ description, md, env, expected }) => { + it(description, () => { + // double quote + expect(md.render(source.join('\n\n'), env)).toEqual( + `${expected.map((item) => item).join('\n')}\n`, + ) + // single quote + expect(md.render(source.join('\n\n').replace(/"/g, "'"), env)).toEqual( + `${expected.map((item) => item.replace(/"/g, "'")).join('\n')}\n`, + ) }) }) }) diff --git a/packages/markdown/tests/plugins/importCodePlugin.spec.ts b/packages/markdown/tests/plugins/importCodePlugin.spec.ts index 08c1d481f7..61f9068907 100644 --- a/packages/markdown/tests/plugins/importCodePlugin.spec.ts +++ b/packages/markdown/tests/plugins/importCodePlugin.spec.ts @@ -34,37 +34,36 @@ afterAll(() => { console.error = consoleError }) -describe('@vuepress/markdown > plugins > importCodePlugin', () => { - it('should not be parsed as import code syntax', () => { - const source = [ - '@[cod', - '@[code', - '@[code]', - '@[code](./foo.js', - '@[code](/path/to/foo.js', - '@[coda](/path/to/foo.js', - ] - - const md = MarkdownIt().use(importCodePlugin) - const env: MarkdownEnv = { - filePath: __filename, - } - const rendered = md.render(source.join('\n\n'), env) - - expect(rendered).toEqual( - `${source.map((item) => `

${item}

`).join('\n')}\n`, - ) - expect(env.importedFiles).toBeUndefined() - }) +it('should not be parsed as import code syntax', () => { + const source = [ + '@[cod', + '@[code', + '@[code]', + '@[code](./foo.js', + '@[code](/path/to/foo.js', + '@[coda](/path/to/foo.js', + ] + + const md = MarkdownIt().use(importCodePlugin) + const env: MarkdownEnv = { + filePath: __filename, + } + const rendered = md.render(source.join('\n\n'), env) + + expect(rendered).toEqual( + `${source.map((item) => `

${item}

`).join('\n')}\n`, + ) + expect(env.importedFiles).toBeUndefined() +}) - describe('lines range', () => { - it('should import all lines', () => { - const source = `\ +describe('lines range', () => { + it('should import all lines', () => { + const source = `\ @[code](${JS_FIXTURE_PATH_RELATIVE}) @[code](${MD_FIXTURE_PATH_RELATIVE}) ` - const expected = `\ + const expected = `\
\
 ${JS_FIXTURE_CONTENT}\
 
@@ -73,18 +72,18 @@ ${MD_FIXTURE_CONTENT}\
` - const md = MarkdownIt().use(importCodePlugin) - const env: MarkdownEnv = { - filePath: __filename, - } - const rendered = md.render(source, env) + const md = MarkdownIt().use(importCodePlugin) + const env: MarkdownEnv = { + filePath: __filename, + } + const rendered = md.render(source, env) - expect(rendered).toEqual(expected) - expect(env.importedFiles).toEqual([JS_FIXTURE_PATH, MD_FIXTURE_PATH]) - }) + expect(rendered).toEqual(expected) + expect(env.importedFiles).toEqual([JS_FIXTURE_PATH, MD_FIXTURE_PATH]) + }) - it('should import partial lines', () => { - const source = `\ + it('should import partial lines', () => { + const source = `\ @[code{1-2}](${JS_FIXTURE_PATH_RELATIVE}) @[code{1-}](${JS_FIXTURE_PATH_RELATIVE}) @[code{2}](${JS_FIXTURE_PATH_RELATIVE}) @@ -93,7 +92,7 @@ ${MD_FIXTURE_CONTENT}\ @[code{5}](${MD_FIXTURE_PATH_RELATIVE}) ` - const expected = `\ + const expected = `\
\
 ${JS_FIXTURE_CONTENT.split('\n').slice(0, 2).join('\n').replace(/\n?$/, '\n')}\
 
@@ -114,246 +113,245 @@ ${MD_FIXTURE_CONTENT.split('\n').slice(4, 5).join('\n').replace(/\n?$/, '\n')}\
` - const md = MarkdownIt().use(importCodePlugin) - const env: MarkdownEnv = { - filePath: __filename, - } - const rendered = md.render(source, env) - - expect(rendered).toEqual(expected) - expect(env.importedFiles).toEqual([ - JS_FIXTURE_PATH, - JS_FIXTURE_PATH, - JS_FIXTURE_PATH, - MD_FIXTURE_PATH, - MD_FIXTURE_PATH, - MD_FIXTURE_PATH, - ]) - }) + const md = MarkdownIt().use(importCodePlugin) + const env: MarkdownEnv = { + filePath: __filename, + } + const rendered = md.render(source, env) + + expect(rendered).toEqual(expected) + expect(env.importedFiles).toEqual([ + JS_FIXTURE_PATH, + JS_FIXTURE_PATH, + JS_FIXTURE_PATH, + MD_FIXTURE_PATH, + MD_FIXTURE_PATH, + MD_FIXTURE_PATH, + ]) }) +}) - describe('code language', () => { - it('should use user defined language', () => { - const source = `\ +describe('code language', () => { + it('should use user defined language', () => { + const source = `\ @[code](/foo.js) @[code ts](/bar.md) ` - const expected = `\ + const expected = `\
File not found
File not found
` - const md = MarkdownIt().use(importCodePlugin) - const env: MarkdownEnv = { - filePath: __filename, - } - const rendered = md.render(source, env) + const md = MarkdownIt().use(importCodePlugin) + const env: MarkdownEnv = { + filePath: __filename, + } + const rendered = md.render(source, env) - expect(rendered).toEqual(expected) - expect(env.importedFiles).toEqual(['/foo.js', '/bar.md']) - expect(mockConsoleError).toHaveBeenCalledTimes(2) - }) + expect(rendered).toEqual(expected) + expect(env.importedFiles).toEqual(['/foo.js', '/bar.md']) + expect(mockConsoleError).toHaveBeenCalledTimes(2) + }) - it('should use file ext as fallback language', () => { - const source = `\ + it('should use file ext as fallback language', () => { + const source = `\ @[code](/foo.js) @[code](/bar.md) ` - const expected = `\ + const expected = `\
File not found
File not found
` - const md = MarkdownIt().use(importCodePlugin) - const env: MarkdownEnv = { - filePath: __filename, - } - const rendered = md.render(source, env) + const md = MarkdownIt().use(importCodePlugin) + const env: MarkdownEnv = { + filePath: __filename, + } + const rendered = md.render(source, env) - expect(rendered).toEqual(expected) - expect(env.importedFiles).toEqual(['/foo.js', '/bar.md']) - expect(mockConsoleError).toHaveBeenCalledTimes(2) - }) + expect(rendered).toEqual(expected) + expect(env.importedFiles).toEqual(['/foo.js', '/bar.md']) + expect(mockConsoleError).toHaveBeenCalledTimes(2) }) +}) - describe('path resolving', () => { - it('should resolve relative path according to filePath', () => { - const source = `\ +describe('path resolving', () => { + it('should resolve relative path according to filePath', () => { + const source = `\ @[code](/foo.js) @[code](./bar.js) ` - const expected = `\ + const expected = `\
File not found
File not found
` - const md = MarkdownIt().use(importCodePlugin) - const env: MarkdownEnv = { - filePath: __filename, - } - const rendered = md.render(source, env) - - expect(rendered).toEqual(expected) - expect(env.importedFiles).toEqual([ - '/foo.js', - path.resolve(__dirname, './bar.js'), - ]) - expect(mockConsoleError).toHaveBeenCalledTimes(2) - }) + const md = MarkdownIt().use(importCodePlugin) + const env: MarkdownEnv = { + filePath: __filename, + } + const rendered = md.render(source, env) + + expect(rendered).toEqual(expected) + expect(env.importedFiles).toEqual([ + '/foo.js', + path.resolve(__dirname, './bar.js'), + ]) + expect(mockConsoleError).toHaveBeenCalledTimes(2) + }) - it('should not resolve relative path if filePath is not provided', () => { - const source = `\ + it('should not resolve relative path if filePath is not provided', () => { + const source = `\ @[code](/foo.js) @[code](./bar.js) ` - const expected = `\ + const expected = `\
File not found
Error when resolving path
` - const md = MarkdownIt().use(importCodePlugin) - const env: MarkdownEnv = { - filePath: null, - } - const rendered = md.render(source, env) + const md = MarkdownIt().use(importCodePlugin) + const env: MarkdownEnv = { + filePath: null, + } + const rendered = md.render(source, env) - expect(rendered).toEqual(expected) - expect(env.importedFiles).toEqual(['/foo.js']) - expect(mockConsoleError).toHaveBeenCalledTimes(2) - }) + expect(rendered).toEqual(expected) + expect(env.importedFiles).toEqual(['/foo.js']) + expect(mockConsoleError).toHaveBeenCalledTimes(2) + }) - it('should handle import path correctly', () => { - const source = `\ + it('should handle import path correctly', () => { + const source = `\ @[code](@fixtures/importCode.js) ` - const expected = `\ + const expected = `\
\
 ${JS_FIXTURE_CONTENT}\
 
` - const md = MarkdownIt().use(importCodePlugin, { - handleImportPath: (str: string): string => - str.replace(/^@fixtures/, path.resolve(__dirname, '../__fixtures__')), - }) - const env: MarkdownEnv = { - filePath: null, - } - const rendered = md.render(source, env) - - expect(rendered).toEqual(expected) - expect(env.importedFiles).toEqual([JS_FIXTURE_PATH]) + const md = MarkdownIt().use(importCodePlugin, { + handleImportPath: (str: string): string => + str.replace(/^@fixtures/, path.resolve(__dirname, '../__fixtures__')), }) + const env: MarkdownEnv = { + filePath: null, + } + const rendered = md.render(source, env) + + expect(rendered).toEqual(expected) + expect(env.importedFiles).toEqual([JS_FIXTURE_PATH]) }) +}) - describe('compatibility with other markdown syntax', () => { - it('should terminate paragraph', () => { - const source = `\ +describe('compatibility with other markdown syntax', () => { + it('should terminate paragraph', () => { + const source = `\ foo @[code](/path/to/foo.js) ` - const expected = `\ + const expected = `\

foo

File not found
` - const md = MarkdownIt().use(importCodePlugin) - const env: MarkdownEnv = { - filePath: __filename, - } - const rendered = md.render(source, env) + const md = MarkdownIt().use(importCodePlugin) + const env: MarkdownEnv = { + filePath: __filename, + } + const rendered = md.render(source, env) - expect(rendered).toEqual(expected) - expect(env.importedFiles).toEqual(['/path/to/foo.js']) - expect(mockConsoleError).toHaveBeenCalledTimes(1) - }) + expect(rendered).toEqual(expected) + expect(env.importedFiles).toEqual(['/path/to/foo.js']) + expect(mockConsoleError).toHaveBeenCalledTimes(1) + }) - it('should terminate blockquote', () => { - const source = `\ + it('should terminate blockquote', () => { + const source = `\ > foo @[code](/path/to/foo.js) ` - const expected = `\ + const expected = `\

foo

File not found
` - const md = MarkdownIt().use(importCodePlugin) - const env: MarkdownEnv = { - filePath: __filename, - } - const rendered = md.render(source, env) + const md = MarkdownIt().use(importCodePlugin) + const env: MarkdownEnv = { + filePath: __filename, + } + const rendered = md.render(source, env) - expect(rendered).toEqual(expected) - expect(env.importedFiles).toEqual(['/path/to/foo.js']) - expect(mockConsoleError).toHaveBeenCalledTimes(1) - }) + expect(rendered).toEqual(expected) + expect(env.importedFiles).toEqual(['/path/to/foo.js']) + expect(mockConsoleError).toHaveBeenCalledTimes(1) }) +}) - describe('compatibility with otherPlugin', () => { - it('should preserve the things after code as fence info', () => { - const source1 = `\ +describe('compatibility with otherPlugin', () => { + it('should preserve the things after code as fence info', () => { + const source1 = `\ @[code js{1,3-4}](${JS_FIXTURE_PATH_RELATIVE}) ` - const source2 = `\ + const source2 = `\ @[code md:no-line-numbers:no-v-pre title="no-line-numbers.md"](${MD_FIXTURE_PATH_RELATIVE}) ` - const md = MarkdownIt().use(importCodePlugin) - const env1: MarkdownEnv = { - filePath: __filename, - } + const md = MarkdownIt().use(importCodePlugin) + const env1: MarkdownEnv = { + filePath: __filename, + } - const rendered1 = md.render(source1, env1) + const rendered1 = md.render(source1, env1) - expect(rendered1).toEqual( - md.render(`\ + expect(rendered1).toEqual( + md.render(`\ \`\`\`js{1,3-4} ${JS_FIXTURE_CONTENT}\ \`\`\` `), - ) - expect(rendered1).toMatchSnapshot() - expect(env1.importedFiles).toEqual([JS_FIXTURE_PATH]) + ) + expect(rendered1).toMatchSnapshot() + expect(env1.importedFiles).toEqual([JS_FIXTURE_PATH]) - const env2: MarkdownEnv = { - filePath: __filename, - } + const env2: MarkdownEnv = { + filePath: __filename, + } - const rendered2 = md.render(source2, env2) + const rendered2 = md.render(source2, env2) - expect(rendered2).toEqual( - md.render(`\ + expect(rendered2).toEqual( + md.render(`\ \`\`\`md:no-line-numbers:no-v-pre title="no-line-numbers.md" ${MD_FIXTURE_CONTENT}\ \`\`\` `), - ) - expect(rendered2).toMatchSnapshot() - expect(env2.importedFiles).toEqual([MD_FIXTURE_PATH]) - }) + ) + expect(rendered2).toMatchSnapshot() + expect(env2.importedFiles).toEqual([MD_FIXTURE_PATH]) + }) - it('should work with syntax supported by vPrePlugin', () => { - const source1 = `\ + it('should work with syntax supported by vPrePlugin', () => { + const source1 = `\ @[code js{1,3-4}](${JS_FIXTURE_PATH_RELATIVE}) ` - const source2 = `\ + const source2 = `\ @[code md:no-line-numbers:no-v-pre title="no-line-numbers.md"](${MD_FIXTURE_PATH_RELATIVE}) ` - const md = MarkdownIt().use(importCodePlugin).use(vPrePlugin) - const env: MarkdownEnv = { - filePath: __filename, - } + const md = MarkdownIt().use(importCodePlugin).use(vPrePlugin) + const env: MarkdownEnv = { + filePath: __filename, + } - const rendered1 = md.render(source1, env) - const rendered2 = md.render(source2, env) + const rendered1 = md.render(source1, env) + const rendered2 = md.render(source2, env) - expect(rendered1).to.contain('v-pre') - expect(rendered2).to.not.contain(' v-pre') - }) + expect(rendered1).to.contain('v-pre') + expect(rendered2).to.not.contain(' v-pre') }) }) diff --git a/packages/markdown/tests/plugins/linksPlugin.spec.ts b/packages/markdown/tests/plugins/linksPlugin.spec.ts index b74996ac9d..17911a6f48 100644 --- a/packages/markdown/tests/plugins/linksPlugin.spec.ts +++ b/packages/markdown/tests/plugins/linksPlugin.spec.ts @@ -3,744 +3,742 @@ import { describe, expect, it } from 'vitest' import type { MarkdownEnv } from '../../src/index.js' import { linksPlugin } from '../../src/index.js' -describe('@vuepress/markdown > plugins > linksPlugin', () => { - describe('external links', () => { - describe('protocol links', () => { - const source = [ - '[https-github](https://github.com)', - '[http-github](http://github.com)', - '[github](//github.com)', - '[https-github-md](https://github.com/foo/bar/blob/main/README.md)', - '[http-github-md](http://github.com/foo/bar/blob/main/README.md)', - '[github-md](//github.com/foo/bar/blob/main/README.md)', - // autolink - '', - '', - '', - '', - ].join('\n\n') - - it('should render default attrs', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin) - const env: MarkdownEnv = {} - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'https-github', - 'http-github', - 'github', - 'https-github-md', - 'http-github-md', - 'github-md', - 'https://github.com', - 'http://github.com', - 'https://github.com/foo/bar/blob/main/README.md', - 'http://github.com/foo/bar/blob/main/README.md', - ] - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - - expect(env.links).toBeUndefined() - }) +describe('external links', () => { + describe('protocol links', () => { + const source = [ + '[https-github](https://github.com)', + '[http-github](http://github.com)', + '[github](//github.com)', + '[https-github-md](https://github.com/foo/bar/blob/main/README.md)', + '[http-github-md](http://github.com/foo/bar/blob/main/README.md)', + '[github-md](//github.com/foo/bar/blob/main/README.md)', + // autolink + '', + '', + '', + '', + ].join('\n\n') + + it('should render default attrs', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin) + const env: MarkdownEnv = {} - it('should add extra attrs', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin, { - externalAttrs: { - foo: 'bar', - }, - }) - const env: MarkdownEnv = {} - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'https-github', - 'http-github', - 'github', - 'https-github-md', - 'http-github-md', - 'github-md', - 'https://github.com', - 'http://github.com', - 'https://github.com/foo/bar/blob/main/README.md', - 'http://github.com/foo/bar/blob/main/README.md', - ] - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - expect(env.links).toBeUndefined() - }) + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + 'https-github', + 'http-github', + 'github', + 'https-github-md', + 'http-github-md', + 'github-md', + 'https://github.com', + 'http://github.com', + 'https://github.com/foo/bar/blob/main/README.md', + 'http://github.com/foo/bar/blob/main/README.md', + ] + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + + expect(env.links).toBeUndefined() + }) - it('should override default attrs', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin, { - externalAttrs: { - rel: 'foobar', - }, - }) - const env: MarkdownEnv = {} - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'https-github', - 'http-github', - 'github', - 'https-github-md', - 'http-github-md', - 'github-md', - 'https://github.com', - 'http://github.com', - 'https://github.com/foo/bar/blob/main/README.md', - 'http://github.com/foo/bar/blob/main/README.md', - ] - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - expect(env.links).toBeUndefined() + it('should add extra attrs', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin, { + externalAttrs: { + foo: 'bar', + }, }) + const env: MarkdownEnv = {} + + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + 'https-github', + 'http-github', + 'github', + 'https-github-md', + 'http-github-md', + 'github-md', + 'https://github.com', + 'http://github.com', + 'https://github.com/foo/bar/blob/main/README.md', + 'http://github.com/foo/bar/blob/main/README.md', + ] + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + expect(env.links).toBeUndefined() }) - describe('absolute links', () => { - const source = [ - '[html](/path/to/index.html)', - '[pdf](/path/to/index.pdf)', - '[png](/path/to/index.png)', - ].join('\n\n') - - it('should render default attrs', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin) - const env: MarkdownEnv = { - base: '/base/', - } - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'html', - 'pdf', - 'png', - ] - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - - expect(env.links).toBeUndefined() + it('should override default attrs', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin, { + externalAttrs: { + rel: 'foobar', + }, }) + const env: MarkdownEnv = {} + + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + 'https-github', + 'http-github', + 'github', + 'https-github-md', + 'http-github-md', + 'github-md', + 'https://github.com', + 'http://github.com', + 'https://github.com/foo/bar/blob/main/README.md', + 'http://github.com/foo/bar/blob/main/README.md', + ] + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + expect(env.links).toBeUndefined() }) }) - describe('internal links', () => { - describe('relative links', () => { - const source = [ - '[foo1](foo.md)', - '[foo2](./foo.md)', - '[bar1](../bar.md)', - '[bar2](./../bar.md)', - '[foobar1](foo/bar.md)', - '[foobar2](../foo/bar.md)', - '[index1](index.md)', - '[index2](./index.md)', - '[index3](../index.md)', - '[index4](../foo/bar/index.md)', - '[readme1](readme.md)', - '[readme2](../foo/bar/readme.md)', - ] - .flatMap((item) => { - const link = /([^)]*)/.exec(item)![1] - - return [ - item, - item.replace(link, `${link}#hash`), - item.replace(link, `${link}?a=1&b=2`), - item.replace(link, `${link}#hash?a=1&b=2`), - item.replace(link, `${link}?a=1&b=2#hash`), - ] - }) - .join('\n\n') - - const expectRelativeLinks = [ - { - raw: 'foo.md', - relative: 'foo.md', - absolute: null, - }, - { - raw: './foo.md', - relative: 'foo.md', - absolute: null, - }, - { - raw: '../bar.md', - relative: '../bar.md', - absolute: null, - }, - { - raw: './../bar.md', - relative: '../bar.md', - absolute: null, - }, - { - raw: 'foo/bar.md', - relative: 'foo/bar.md', - absolute: null, - }, - { - raw: '../foo/bar.md', - relative: '../foo/bar.md', - absolute: null, - }, - { - raw: 'index.md', - relative: 'index.md', - absolute: null, - }, - { - raw: './index.md', - relative: 'index.md', - absolute: null, - }, - { - raw: '../index.md', - relative: '../index.md', - absolute: null, - }, - { - raw: '../foo/bar/index.md', - relative: '../foo/bar/index.md', - absolute: null, - }, - { - raw: 'readme.md', - relative: 'readme.md', - absolute: null, - }, - { - raw: '../foo/bar/readme.md', - relative: '../foo/bar/readme.md', - absolute: null, - }, - ].flatMap(({ raw, ...rest }) => [ - { raw, ...rest }, - { raw: `${raw}#hash`, ...rest }, - { raw: `${raw}?a=1&b=2`, ...rest }, - { raw: `${raw}#hash?a=1&b=2`, ...rest }, - { raw: `${raw}?a=1&b=2#hash`, ...rest }, - ]) + describe('absolute links', () => { + const source = [ + '[html](/path/to/index.html)', + '[pdf](/path/to/index.pdf)', + '[png](/path/to/index.png)', + ].join('\n\n') - it('should render to tag correctly', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin, { - internalTag: 'a', - }) - const env: MarkdownEnv = {} - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'foo1', - 'foo2', - 'bar1', - 'bar2', - 'foobar1', - 'foobar2', - 'index1', - 'index2', - 'index3', - 'index4', - 'readme1', - 'readme2', - ] - .flatMap((item) => { - const link = /href="([^"]*)"/.exec(item)![1] - - return [ - item, - item.replace(link, `${link}#hash`), - item.replace(link, `${link}?a=1&b=2`), - item.replace(link, `${link}#hash?a=1&b=2`), - item.replace(link, `${link}?a=1&b=2#hash`), - ] - }) - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - - expect(env.links).toEqual(expectRelativeLinks) - }) + it('should render default attrs', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin) + const env: MarkdownEnv = { + base: '/base/', + } + + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + 'html', + 'pdf', + 'png', + ] + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) - it('should render relative links correctly', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin) - const env: MarkdownEnv = {} - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'foo1', - 'foo2', - 'bar1', - 'bar2', - 'foobar1', - 'foobar2', - 'index1', - 'index2', - 'index3', - 'index4', - 'readme1', - 'readme2', - ] - .flatMap((item) => { - const link = /to="([^"]*)"/.exec(item)![1] - - return [ - item, - item.replace(link, `${link}#hash`), - item.replace(link, `${link}?a=1&b=2`), - item.replace(link, `${link}#hash?a=1&b=2`), - item.replace(link, `${link}?a=1&b=2#hash`), - ] - }) - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - - expect(env.links).toEqual(expectRelativeLinks) - }) + expect(env.links).toBeUndefined() + }) + }) +}) - it('should convert to absolute links correctly', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin) - const env: MarkdownEnv = { - filePathRelative: 'path/to/file.md', - } - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'foo1', - 'foo2', - 'bar1', - 'bar2', - 'foobar1', - 'foobar2', - 'index1', - 'index2', - 'index3', - 'index4', - 'readme1', - 'readme2', - ] - .flatMap((item) => { - const link = /to="([^"]*)"/.exec(item)![1] - - return [ - item, - item.replace(link, `${link}#hash`), - item.replace(link, `${link}?a=1&b=2`), - item.replace(link, `${link}#hash?a=1&b=2`), - item.replace(link, `${link}?a=1&b=2#hash`), - ] - }) - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - - expect(env.links).toEqual( - [ - { - raw: 'foo.md', - relative: 'path/to/foo.md', - absolute: '/path/to/foo.md', - }, - { - raw: './foo.md', - relative: 'path/to/foo.md', - absolute: '/path/to/foo.md', - }, - { - raw: '../bar.md', - relative: 'path/bar.md', - absolute: '/path/bar.md', - }, - { - raw: './../bar.md', - relative: 'path/bar.md', - absolute: '/path/bar.md', - }, - { - raw: 'foo/bar.md', - relative: 'path/to/foo/bar.md', - absolute: '/path/to/foo/bar.md', - }, - { - raw: '../foo/bar.md', - relative: 'path/foo/bar.md', - absolute: '/path/foo/bar.md', - }, - { - raw: 'index.md', - relative: 'path/to/index.md', - absolute: '/path/to/index.md', - }, - { - raw: './index.md', - relative: 'path/to/index.md', - absolute: '/path/to/index.md', - }, - { - raw: '../index.md', - relative: 'path/index.md', - absolute: '/path/index.md', - }, - { - raw: '../foo/bar/index.md', - relative: 'path/foo/bar/index.md', - absolute: '/path/foo/bar/index.md', - }, - { - raw: 'readme.md', - relative: 'path/to/readme.md', - absolute: '/path/to/readme.md', - }, - { - raw: '../foo/bar/readme.md', - relative: 'path/foo/bar/readme.md', - absolute: '/path/foo/bar/readme.md', - }, - ].flatMap(({ raw, ...rest }) => [ - { raw, ...rest }, - { raw: `${raw}#hash`, ...rest }, - { raw: `${raw}?a=1&b=2`, ...rest }, - { raw: `${raw}#hash?a=1&b=2`, ...rest }, - { raw: `${raw}?a=1&b=2#hash`, ...rest }, - ]), - ) +describe('internal links', () => { + describe('relative links', () => { + const source = [ + '[foo1](foo.md)', + '[foo2](./foo.md)', + '[bar1](../bar.md)', + '[bar2](./../bar.md)', + '[foobar1](foo/bar.md)', + '[foobar2](../foo/bar.md)', + '[index1](index.md)', + '[index2](./index.md)', + '[index3](../index.md)', + '[index4](../foo/bar/index.md)', + '[readme1](readme.md)', + '[readme2](../foo/bar/readme.md)', + ] + .flatMap((item) => { + const link = /([^)]*)/.exec(item)![1] + + return [ + item, + item.replace(link, `${link}#hash`), + item.replace(link, `${link}?a=1&b=2`), + item.replace(link, `${link}#hash?a=1&b=2`), + item.replace(link, `${link}?a=1&b=2#hash`), + ] }) - - it('should convert to absolute links correctly if the file path contains non-ASCII characters', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin) - const env: MarkdownEnv = { - filePathRelative: '中/文/路径.md', - } - const encoded中 = encodeURI('中') - const encoded文 = encodeURI('文') - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - `foo1`, - `foo2`, - `bar1`, - `bar2`, - `foobar1`, - `foobar2`, - `index1`, - `index2`, - `index3`, - `index4`, - `readme1`, - `readme2`, - ] - .flatMap((item) => { - const link = /to="([^"]*)"/.exec(item)![1] - - return [ - item, - item.replace(link, `${link}#hash`), - item.replace(link, `${link}?a=1&b=2`), - item.replace(link, `${link}#hash?a=1&b=2`), - item.replace(link, `${link}?a=1&b=2#hash`), - ] - }) - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - - expect(env.links).toEqual( - [ - { - raw: 'foo.md', - relative: `${encoded中}/${encoded文}/foo.md`, - absolute: `/${encoded中}/${encoded文}/foo.md`, - }, - { - raw: './foo.md', - relative: `${encoded中}/${encoded文}/foo.md`, - absolute: `/${encoded中}/${encoded文}/foo.md`, - }, - { - raw: '../bar.md', - relative: `${encoded中}/bar.md`, - absolute: `/${encoded中}/bar.md`, - }, - { - raw: './../bar.md', - relative: `${encoded中}/bar.md`, - absolute: `/${encoded中}/bar.md`, - }, - { - raw: 'foo/bar.md', - relative: `${encoded中}/${encoded文}/foo/bar.md`, - absolute: `/${encoded中}/${encoded文}/foo/bar.md`, - }, - { - raw: '../foo/bar.md', - relative: `${encoded中}/foo/bar.md`, - absolute: `/${encoded中}/foo/bar.md`, - }, - { - raw: 'index.md', - relative: `${encoded中}/${encoded文}/index.md`, - absolute: `/${encoded中}/${encoded文}/index.md`, - }, - { - raw: './index.md', - relative: `${encoded中}/${encoded文}/index.md`, - absolute: `/${encoded中}/${encoded文}/index.md`, - }, - { - raw: '../index.md', - relative: `${encoded中}/index.md`, - absolute: `/${encoded中}/index.md`, - }, - { - raw: '../foo/bar/index.md', - relative: `${encoded中}/foo/bar/index.md`, - absolute: `/${encoded中}/foo/bar/index.md`, - }, - { - raw: 'readme.md', - relative: `${encoded中}/${encoded文}/readme.md`, - absolute: `/${encoded中}/${encoded文}/readme.md`, - }, - { - raw: '../foo/bar/readme.md', - relative: `${encoded中}/foo/bar/readme.md`, - absolute: `/${encoded中}/foo/bar/readme.md`, - }, - ].flatMap(({ raw, ...rest }) => [ - { raw, ...rest }, - { raw: `${raw}#hash`, ...rest }, - { raw: `${raw}?a=1&b=2`, ...rest }, - { raw: `${raw}#hash?a=1&b=2`, ...rest }, - { raw: `${raw}?a=1&b=2#hash`, ...rest }, - ]), - ) + .join('\n\n') + + const expectRelativeLinks = [ + { + raw: 'foo.md', + relative: 'foo.md', + absolute: null, + }, + { + raw: './foo.md', + relative: 'foo.md', + absolute: null, + }, + { + raw: '../bar.md', + relative: '../bar.md', + absolute: null, + }, + { + raw: './../bar.md', + relative: '../bar.md', + absolute: null, + }, + { + raw: 'foo/bar.md', + relative: 'foo/bar.md', + absolute: null, + }, + { + raw: '../foo/bar.md', + relative: '../foo/bar.md', + absolute: null, + }, + { + raw: 'index.md', + relative: 'index.md', + absolute: null, + }, + { + raw: './index.md', + relative: 'index.md', + absolute: null, + }, + { + raw: '../index.md', + relative: '../index.md', + absolute: null, + }, + { + raw: '../foo/bar/index.md', + relative: '../foo/bar/index.md', + absolute: null, + }, + { + raw: 'readme.md', + relative: 'readme.md', + absolute: null, + }, + { + raw: '../foo/bar/readme.md', + relative: '../foo/bar/readme.md', + absolute: null, + }, + ].flatMap(({ raw, ...rest }) => [ + { raw, ...rest }, + { raw: `${raw}#hash`, ...rest }, + { raw: `${raw}?a=1&b=2`, ...rest }, + { raw: `${raw}#hash?a=1&b=2`, ...rest }, + { raw: `${raw}?a=1&b=2#hash`, ...rest }, + ]) + + it('should render to tag correctly', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin, { + internalTag: 'a', }) + const env: MarkdownEnv = {} - it('should not conflict with base', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin) - const env: MarkdownEnv = { - base: '/path/', - filePathRelative: 'path/to/file.md', - } - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'foo1', - 'foo2', - 'bar1', - 'bar2', - 'foobar1', - 'foobar2', - 'index1', - 'index2', - 'index3', - 'index4', - 'readme1', - 'readme2', - ] - .flatMap((item) => { - const link = /to="([^"]*)"/.exec(item)![1] - - return [ - item, - item.replace(link, `${link}#hash`), - item.replace(link, `${link}?a=1&b=2`), - item.replace(link, `${link}#hash?a=1&b=2`), - item.replace(link, `${link}?a=1&b=2#hash`), - ] - }) - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - - expect(env.links).toEqual( - [ - { - raw: 'foo.md', - relative: 'path/to/foo.md', - absolute: '/path/path/to/foo.md', - }, - { - raw: './foo.md', - relative: 'path/to/foo.md', - absolute: '/path/path/to/foo.md', - }, - { - raw: '../bar.md', - relative: 'path/bar.md', - absolute: '/path/path/bar.md', - }, - { - raw: './../bar.md', - relative: 'path/bar.md', - absolute: '/path/path/bar.md', - }, - { - raw: 'foo/bar.md', - relative: 'path/to/foo/bar.md', - absolute: '/path/path/to/foo/bar.md', - }, - { - raw: '../foo/bar.md', - relative: 'path/foo/bar.md', - absolute: '/path/path/foo/bar.md', - }, - { - raw: 'index.md', - relative: 'path/to/index.md', - absolute: '/path/path/to/index.md', - }, - { - raw: './index.md', - relative: 'path/to/index.md', - absolute: '/path/path/to/index.md', - }, - { - raw: '../index.md', - relative: 'path/index.md', - absolute: '/path/path/index.md', - }, - { - raw: '../foo/bar/index.md', - relative: 'path/foo/bar/index.md', - absolute: '/path/path/foo/bar/index.md', - }, - { - raw: 'readme.md', - relative: 'path/to/readme.md', - absolute: '/path/path/to/readme.md', - }, - { - raw: '../foo/bar/readme.md', - relative: 'path/foo/bar/readme.md', - absolute: '/path/path/foo/bar/readme.md', - }, - ].flatMap(({ raw, ...rest }) => [ - { raw, ...rest }, - { raw: `${raw}#hash`, ...rest }, - { raw: `${raw}?a=1&b=2`, ...rest }, - { raw: `${raw}#hash?a=1&b=2`, ...rest }, - { raw: `${raw}?a=1&b=2#hash`, ...rest }, - ]), - ) - }) + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + '
foo1', + 'foo2', + 'bar1', + 'bar2', + 'foobar1', + 'foobar2', + 'index1', + 'index2', + 'index3', + 'index4', + 'readme1', + 'readme2', + ] + .flatMap((item) => { + const link = /href="([^"]*)"/.exec(item)![1] + + return [ + item, + item.replace(link, `${link}#hash`), + item.replace(link, `${link}?a=1&b=2`), + item.replace(link, `${link}#hash?a=1&b=2`), + item.replace(link, `${link}?a=1&b=2#hash`), + ] + }) + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + + expect(env.links).toEqual(expectRelativeLinks) }) - describe('absolute links', () => { - const source = [ - '[md](/path/to/index.md)', - '[md-with-redundant-base](/base/path/to/index.md)', - '[html](/base/path/to/index.html)', - ].join('\n\n') - - it('should resolve to internal links correctly', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin) - const env: MarkdownEnv = { - base: '/base/', - } - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'md', - 'md-with-redundant-base', - 'html', - ] - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - - expect(env.links).toEqual([ - { - raw: '/path/to/index.md', + it('should render relative links correctly', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin) + const env: MarkdownEnv = {} + + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + 'foo1', + 'foo2', + 'bar1', + 'bar2', + 'foobar1', + 'foobar2', + 'index1', + 'index2', + 'index3', + 'index4', + 'readme1', + 'readme2', + ] + .flatMap((item) => { + const link = /to="([^"]*)"/.exec(item)![1] + + return [ + item, + item.replace(link, `${link}#hash`), + item.replace(link, `${link}?a=1&b=2`), + item.replace(link, `${link}#hash?a=1&b=2`), + item.replace(link, `${link}?a=1&b=2#hash`), + ] + }) + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + + expect(env.links).toEqual(expectRelativeLinks) + }) + + it('should convert to absolute links correctly', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin) + const env: MarkdownEnv = { + filePathRelative: 'path/to/file.md', + } + + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + 'foo1', + 'foo2', + 'bar1', + 'bar2', + 'foobar1', + 'foobar2', + 'index1', + 'index2', + 'index3', + 'index4', + 'readme1', + 'readme2', + ] + .flatMap((item) => { + const link = /to="([^"]*)"/.exec(item)![1] + + return [ + item, + item.replace(link, `${link}#hash`), + item.replace(link, `${link}?a=1&b=2`), + item.replace(link, `${link}#hash?a=1&b=2`), + item.replace(link, `${link}?a=1&b=2#hash`), + ] + }) + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + + expect(env.links).toEqual( + [ + { + raw: 'foo.md', + relative: 'path/to/foo.md', + absolute: '/path/to/foo.md', + }, + { + raw: './foo.md', + relative: 'path/to/foo.md', + absolute: '/path/to/foo.md', + }, + { + raw: '../bar.md', + relative: 'path/bar.md', + absolute: '/path/bar.md', + }, + { + raw: './../bar.md', + relative: 'path/bar.md', + absolute: '/path/bar.md', + }, + { + raw: 'foo/bar.md', + relative: 'path/to/foo/bar.md', + absolute: '/path/to/foo/bar.md', + }, + { + raw: '../foo/bar.md', + relative: 'path/foo/bar.md', + absolute: '/path/foo/bar.md', + }, + { + raw: 'index.md', relative: 'path/to/index.md', - absolute: '/base/path/to/index.md', + absolute: '/path/to/index.md', }, { - raw: '/base/path/to/index.md', - relative: 'base/path/to/index.md', - absolute: '/base/base/path/to/index.md', + raw: './index.md', + relative: 'path/to/index.md', + absolute: '/path/to/index.md', }, { - raw: '/base/path/to/index.html', - relative: 'path/to/index.html', - absolute: '/base/path/to/index.html', + raw: '../index.md', + relative: 'path/index.md', + absolute: '/path/index.md', }, - ]) - }) + { + raw: '../foo/bar/index.md', + relative: 'path/foo/bar/index.md', + absolute: '/path/foo/bar/index.md', + }, + { + raw: 'readme.md', + relative: 'path/to/readme.md', + absolute: '/path/to/readme.md', + }, + { + raw: '../foo/bar/readme.md', + relative: 'path/foo/bar/readme.md', + absolute: '/path/foo/bar/readme.md', + }, + ].flatMap(({ raw, ...rest }) => [ + { raw, ...rest }, + { raw: `${raw}#hash`, ...rest }, + { raw: `${raw}?a=1&b=2`, ...rest }, + { raw: `${raw}#hash?a=1&b=2`, ...rest }, + { raw: `${raw}?a=1&b=2#hash`, ...rest }, + ]), + ) + }) + + it('should convert to absolute links correctly if the file path contains non-ASCII characters', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin) + const env: MarkdownEnv = { + filePathRelative: '中/文/路径.md', + } + const encoded中 = encodeURI('中') + const encoded文 = encodeURI('文') + + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + `foo1`, + `foo2`, + `bar1`, + `bar2`, + `foobar1`, + `foobar2`, + `index1`, + `index2`, + `index3`, + `index4`, + `readme1`, + `readme2`, + ] + .flatMap((item) => { + const link = /to="([^"]*)"/.exec(item)![1] + + return [ + item, + item.replace(link, `${link}#hash`), + item.replace(link, `${link}?a=1&b=2`), + item.replace(link, `${link}#hash?a=1&b=2`), + item.replace(link, `${link}?a=1&b=2#hash`), + ] + }) + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + + expect(env.links).toEqual( + [ + { + raw: 'foo.md', + relative: `${encoded中}/${encoded文}/foo.md`, + absolute: `/${encoded中}/${encoded文}/foo.md`, + }, + { + raw: './foo.md', + relative: `${encoded中}/${encoded文}/foo.md`, + absolute: `/${encoded中}/${encoded文}/foo.md`, + }, + { + raw: '../bar.md', + relative: `${encoded中}/bar.md`, + absolute: `/${encoded中}/bar.md`, + }, + { + raw: './../bar.md', + relative: `${encoded中}/bar.md`, + absolute: `/${encoded中}/bar.md`, + }, + { + raw: 'foo/bar.md', + relative: `${encoded中}/${encoded文}/foo/bar.md`, + absolute: `/${encoded中}/${encoded文}/foo/bar.md`, + }, + { + raw: '../foo/bar.md', + relative: `${encoded中}/foo/bar.md`, + absolute: `/${encoded中}/foo/bar.md`, + }, + { + raw: 'index.md', + relative: `${encoded中}/${encoded文}/index.md`, + absolute: `/${encoded中}/${encoded文}/index.md`, + }, + { + raw: './index.md', + relative: `${encoded中}/${encoded文}/index.md`, + absolute: `/${encoded中}/${encoded文}/index.md`, + }, + { + raw: '../index.md', + relative: `${encoded中}/index.md`, + absolute: `/${encoded中}/index.md`, + }, + { + raw: '../foo/bar/index.md', + relative: `${encoded中}/foo/bar/index.md`, + absolute: `/${encoded中}/foo/bar/index.md`, + }, + { + raw: 'readme.md', + relative: `${encoded中}/${encoded文}/readme.md`, + absolute: `/${encoded中}/${encoded文}/readme.md`, + }, + { + raw: '../foo/bar/readme.md', + relative: `${encoded中}/foo/bar/readme.md`, + absolute: `/${encoded中}/foo/bar/readme.md`, + }, + ].flatMap(({ raw, ...rest }) => [ + { raw, ...rest }, + { raw: `${raw}#hash`, ...rest }, + { raw: `${raw}?a=1&b=2`, ...rest }, + { raw: `${raw}#hash?a=1&b=2`, ...rest }, + { raw: `${raw}?a=1&b=2#hash`, ...rest }, + ]), + ) + }) - it('should render to tag correctly', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin, { - internalTag: 'a', - }) - const env: MarkdownEnv = { - base: '/base/', - } - - const rendered = md.render(source, env) - - expect(rendered).toEqual( - [ - 'md', - 'md-with-redundant-base', - 'html', - ] - .map((a) => `

${a}

`) - .join('\n') + '\n', - ) - - expect(env.links).toEqual([ - { - raw: '/path/to/index.md', + it('should not conflict with base', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin) + const env: MarkdownEnv = { + base: '/path/', + filePathRelative: 'path/to/file.md', + } + + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + 'foo1', + 'foo2', + 'bar1', + 'bar2', + 'foobar1', + 'foobar2', + 'index1', + 'index2', + 'index3', + 'index4', + 'readme1', + 'readme2', + ] + .flatMap((item) => { + const link = /to="([^"]*)"/.exec(item)![1] + + return [ + item, + item.replace(link, `${link}#hash`), + item.replace(link, `${link}?a=1&b=2`), + item.replace(link, `${link}#hash?a=1&b=2`), + item.replace(link, `${link}?a=1&b=2#hash`), + ] + }) + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + + expect(env.links).toEqual( + [ + { + raw: 'foo.md', + relative: 'path/to/foo.md', + absolute: '/path/path/to/foo.md', + }, + { + raw: './foo.md', + relative: 'path/to/foo.md', + absolute: '/path/path/to/foo.md', + }, + { + raw: '../bar.md', + relative: 'path/bar.md', + absolute: '/path/path/bar.md', + }, + { + raw: './../bar.md', + relative: 'path/bar.md', + absolute: '/path/path/bar.md', + }, + { + raw: 'foo/bar.md', + relative: 'path/to/foo/bar.md', + absolute: '/path/path/to/foo/bar.md', + }, + { + raw: '../foo/bar.md', + relative: 'path/foo/bar.md', + absolute: '/path/path/foo/bar.md', + }, + { + raw: 'index.md', relative: 'path/to/index.md', - absolute: '/base/path/to/index.md', + absolute: '/path/path/to/index.md', }, { - raw: '/base/path/to/index.md', - relative: 'base/path/to/index.md', - absolute: '/base/base/path/to/index.md', + raw: './index.md', + relative: 'path/to/index.md', + absolute: '/path/path/to/index.md', }, { - raw: '/base/path/to/index.html', - relative: 'path/to/index.html', - absolute: '/base/path/to/index.html', + raw: '../index.md', + relative: 'path/index.md', + absolute: '/path/path/index.md', }, - ]) - }) + { + raw: '../foo/bar/index.md', + relative: 'path/foo/bar/index.md', + absolute: '/path/path/foo/bar/index.md', + }, + { + raw: 'readme.md', + relative: 'path/to/readme.md', + absolute: '/path/path/to/readme.md', + }, + { + raw: '../foo/bar/readme.md', + relative: 'path/foo/bar/readme.md', + absolute: '/path/path/foo/bar/readme.md', + }, + ].flatMap(({ raw, ...rest }) => [ + { raw, ...rest }, + { raw: `${raw}#hash`, ...rest }, + { raw: `${raw}?a=1&b=2`, ...rest }, + { raw: `${raw}#hash?a=1&b=2`, ...rest }, + { raw: `${raw}?a=1&b=2#hash`, ...rest }, + ]), + ) }) }) - describe('empty links', () => { - it('should render correctly', () => { - const md = MarkdownIt({ html: true }).use(linksPlugin) - const env: MarkdownEnv = {} + describe('absolute links', () => { + const source = [ + '[md](/path/to/index.md)', + '[md-with-redundant-base](/base/path/to/index.md)', + '[html](/base/path/to/index.html)', + ].join('\n\n') - const rendered = md.render('[empty]()', env) + it('should resolve to internal links correctly', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin) + const env: MarkdownEnv = { + base: '/base/', + } + + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + 'md', + 'md-with-redundant-base', + 'html', + ] + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + + expect(env.links).toEqual([ + { + raw: '/path/to/index.md', + relative: 'path/to/index.md', + absolute: '/base/path/to/index.md', + }, + { + raw: '/base/path/to/index.md', + relative: 'base/path/to/index.md', + absolute: '/base/base/path/to/index.md', + }, + { + raw: '/base/path/to/index.html', + relative: 'path/to/index.html', + absolute: '/base/path/to/index.html', + }, + ]) + }) - expect(rendered).toEqual('

empty

\n') - expect(env.links).toBeUndefined() + it('should render to tag correctly', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin, { + internalTag: 'a', + }) + const env: MarkdownEnv = { + base: '/base/', + } + + const rendered = md.render(source, env) + + expect(rendered).toEqual( + [ + 'md', + 'md-with-redundant-base', + 'html', + ] + .map((a) => `

${a}

`) + .join('\n') + '\n', + ) + + expect(env.links).toEqual([ + { + raw: '/path/to/index.md', + relative: 'path/to/index.md', + absolute: '/base/path/to/index.md', + }, + { + raw: '/base/path/to/index.md', + relative: 'base/path/to/index.md', + absolute: '/base/base/path/to/index.md', + }, + { + raw: '/base/path/to/index.html', + relative: 'path/to/index.html', + absolute: '/base/path/to/index.html', + }, + ]) }) }) }) + +describe('empty links', () => { + it('should render correctly', () => { + const md = MarkdownIt({ html: true }).use(linksPlugin) + const env: MarkdownEnv = {} + + const rendered = md.render('[empty]()', env) + + expect(rendered).toEqual('

empty

\n') + expect(env.links).toBeUndefined() + }) +}) diff --git a/packages/markdown/tests/plugins/vPrePlugin.spec.ts b/packages/markdown/tests/plugins/vPrePlugin.spec.ts index 128a163cf5..97610966fe 100644 --- a/packages/markdown/tests/plugins/vPrePlugin.spec.ts +++ b/packages/markdown/tests/plugins/vPrePlugin.spec.ts @@ -4,42 +4,41 @@ import { vPrePlugin } from '../../src/index.js' const CODE_FENCE = '```' -describe('@vuepress/markdown > plugins > vPrePlugin', () => { - describe('plugin options', () => { - const source = `\ +describe('plugin options', () => { + const source = `\ ${CODE_FENCE}js const a = 1 ${CODE_FENCE} \`inline\` ` - it('should process code with default options', () => { - const md = MarkdownIt().use(vPrePlugin) + it('should process code with default options', () => { + const md = MarkdownIt().use(vPrePlugin) - expect(md.render(source)).toMatchSnapshot() - }) + expect(md.render(source)).toMatchSnapshot() + }) - it('should disable `block`', () => { - const md = MarkdownIt().use(vPrePlugin, { block: false }) + it('should disable `block`', () => { + const md = MarkdownIt().use(vPrePlugin, { block: false }) - expect(md.render(source)).toMatchSnapshot() - }) + expect(md.render(source)).toMatchSnapshot() + }) - it('should disable `inline`', () => { - const md = MarkdownIt().use(vPrePlugin, { inline: false }) + it('should disable `inline`', () => { + const md = MarkdownIt().use(vPrePlugin, { inline: false }) - expect(md.render(source)).toMatchSnapshot() - }) + expect(md.render(source)).toMatchSnapshot() + }) - it('should disable `block` and `inline`', () => { - const md = MarkdownIt().use(vPrePlugin, { block: false, inline: false }) + it('should disable `block` and `inline`', () => { + const md = MarkdownIt().use(vPrePlugin, { block: false, inline: false }) - expect(md.render(source)).toMatchSnapshot() - }) + expect(md.render(source)).toMatchSnapshot() }) +}) - describe(':v-pre / :no-v-pre', () => { - const source = `\ +describe(':v-pre / :no-v-pre', () => { + const source = `\ ${CODE_FENCE}js:v-pre const a = 1 ${CODE_FENCE} @@ -68,21 +67,21 @@ ${CODE_FENCE}js const a = 1 ${CODE_FENCE} ` - it('should work if `block` is enabled by default', () => { - const md = MarkdownIt().use(vPrePlugin) + it('should work if `block` is enabled by default', () => { + const md = MarkdownIt().use(vPrePlugin) - expect(md.render(source)).toMatchSnapshot() - }) + expect(md.render(source)).toMatchSnapshot() + }) - it('should work if `block` is disabled', () => { - const md = MarkdownIt().use(vPrePlugin, { block: false }) + it('should work if `block` is disabled', () => { + const md = MarkdownIt().use(vPrePlugin, { block: false }) - expect(md.render(source)).toMatchSnapshot() - }) + expect(md.render(source)).toMatchSnapshot() }) +}) - describe('syntax highlighting', () => { - const source = `\ +describe('syntax highlighting', () => { + const source = `\ ${CODE_FENCE}js:v-pre const a = 1 ${CODE_FENCE} @@ -111,26 +110,25 @@ ${CODE_FENCE}js const a = 1 ${CODE_FENCE} ` - it('should work highlighted code is wrapped with `
`', () => {
-      const highlight = vi.fn(
-        (code, lang) =>
-          `
highlighted code: ${code}, lang: ${lang}
`, - ) - - const md = MarkdownIt({ highlight }).use(vPrePlugin) - - expect(md.render(source)).toMatchSnapshot() - expect(highlight).toHaveBeenCalledTimes(7) - }) - - it('should work if highlighted code is not wrapped with `
`', () => {
-      const highlight = vi.fn(
-        (code, lang) => `highlighted code: ${code}, lang: ${lang}`,
-      )
-      const md = MarkdownIt({ highlight }).use(vPrePlugin)
-
-      expect(md.render(source)).toMatchSnapshot()
-      expect(highlight).toHaveBeenCalledTimes(7)
-    })
+  it('should work highlighted code is wrapped with `
`', () => {
+    const highlight = vi.fn(
+      (code, lang) =>
+        `
highlighted code: ${code}, lang: ${lang}
`, + ) + + const md = MarkdownIt({ highlight }).use(vPrePlugin) + + expect(md.render(source)).toMatchSnapshot() + expect(highlight).toHaveBeenCalledTimes(7) + }) + + it('should work if highlighted code is not wrapped with `
`', () => {
+    const highlight = vi.fn(
+      (code, lang) => `highlighted code: ${code}, lang: ${lang}`,
+    )
+    const md = MarkdownIt({ highlight }).use(vPrePlugin)
+
+    expect(md.render(source)).toMatchSnapshot()
+    expect(highlight).toHaveBeenCalledTimes(7)
   })
 })