mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-10 06:42:31 +02:00
fix(mdx-loader): resolve Markdown/MDX links with Remark instead of RegExp (#10168)
This commit is contained in:
parent
aab332c2ae
commit
e34614963e
36 changed files with 902 additions and 1620 deletions
|
@ -1,250 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`replaceMarkdownLinks does basic replace 1`] = `
|
||||
{
|
||||
"brokenMarkdownLinks": [
|
||||
{
|
||||
"contentPaths": {
|
||||
"contentPath": "docs",
|
||||
"contentPathLocalized": "i18n/docs-localized",
|
||||
},
|
||||
"filePath": "docs/intro.md",
|
||||
"link": "hmmm.md",
|
||||
},
|
||||
],
|
||||
"newContent": "
|
||||
[foo](/doc/foo)
|
||||
[baz](/doc/baz)
|
||||
[foo](/doc/foo)
|
||||
[http](http://github.com/facebook/docusaurus/README.md)
|
||||
[https](https://github.com/facebook/docusaurus/README.md)
|
||||
[asset](./foo.js)
|
||||
[asset as well](@site/docs/_partial.md)
|
||||
[looks like http...](/doc/http)
|
||||
[nonexistent](hmmm.md)
|
||||
",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`replaceMarkdownLinks handles link titles 1`] = `
|
||||
{
|
||||
"brokenMarkdownLinks": [],
|
||||
"newContent": "
|
||||
[URL](/docs/file "title")
|
||||
[URL](/docs/file 'title')
|
||||
[URL](/docs/file (title))
|
||||
",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`replaceMarkdownLinks handles stray spaces 1`] = `
|
||||
{
|
||||
"brokenMarkdownLinks": [],
|
||||
"newContent": "
|
||||
[URL]( /docs/file )
|
||||
[ref]: /docs/file
|
||||
",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`replaceMarkdownLinks handles unpaired fences 1`] = `
|
||||
{
|
||||
"brokenMarkdownLinks": [],
|
||||
"newContent": "
|
||||
\`\`\`foo
|
||||
hello
|
||||
|
||||
\`\`\`foo
|
||||
hello
|
||||
\`\`\`
|
||||
|
||||
A [link](/docs/file)
|
||||
",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`replaceMarkdownLinks ignores links in HTML comments 1`] = `
|
||||
{
|
||||
"brokenMarkdownLinks": [
|
||||
{
|
||||
"contentPaths": {
|
||||
"contentPath": "docs",
|
||||
"contentPathLocalized": "i18n/docs-localized",
|
||||
},
|
||||
"filePath": "docs/intro.md",
|
||||
"link": "./foo.md",
|
||||
},
|
||||
{
|
||||
"contentPaths": {
|
||||
"contentPath": "docs",
|
||||
"contentPathLocalized": "i18n/docs-localized",
|
||||
},
|
||||
"filePath": "docs/intro.md",
|
||||
"link": "./foo.md",
|
||||
},
|
||||
],
|
||||
"newContent": "
|
||||
<!-- [foo](./foo.md) -->
|
||||
<!--
|
||||
[foo](./foo.md)
|
||||
-->
|
||||
",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`replaceMarkdownLinks ignores links in fenced blocks 1`] = `
|
||||
{
|
||||
"brokenMarkdownLinks": [],
|
||||
"newContent": "
|
||||
\`\`\`
|
||||
[foo](foo.md)
|
||||
\`\`\`
|
||||
|
||||
\`\`\`\`js
|
||||
[foo](foo.md)
|
||||
\`\`\`
|
||||
[foo](foo.md)
|
||||
\`\`\`
|
||||
[foo](foo.md)
|
||||
\`\`\`\`
|
||||
|
||||
\`\`\`\`js
|
||||
[foo](foo.md)
|
||||
\`\`\`
|
||||
[foo](foo.md)
|
||||
\`\`\`\`
|
||||
|
||||
~~~js
|
||||
[foo](foo.md)
|
||||
~~~
|
||||
|
||||
~~~js
|
||||
[foo](foo.md)
|
||||
\`\`\`
|
||||
[foo](foo.md)
|
||||
\`\`\`
|
||||
[foo](foo.md)
|
||||
~~~
|
||||
",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`replaceMarkdownLinks ignores links in inline code 1`] = `
|
||||
{
|
||||
"brokenMarkdownLinks": [
|
||||
{
|
||||
"contentPaths": {
|
||||
"contentPath": "docs",
|
||||
"contentPathLocalized": "i18n/docs-localized",
|
||||
},
|
||||
"filePath": "docs/intro.md",
|
||||
"link": "foo.md",
|
||||
},
|
||||
],
|
||||
"newContent": "
|
||||
\`[foo](foo.md)\`
|
||||
",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`replaceMarkdownLinks preserves query/hash 1`] = `
|
||||
{
|
||||
"brokenMarkdownLinks": [],
|
||||
"newContent": "
|
||||
[URL](/docs/file?foo=bar#baz)
|
||||
[URL](/docs/file#a)
|
||||
[URL](/docs/file?c)
|
||||
",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`replaceMarkdownLinks replaces Markdown links with spaces 1`] = `
|
||||
{
|
||||
"brokenMarkdownLinks": [],
|
||||
"newContent": "
|
||||
[doc a](/docs/doc%20a)
|
||||
[doc a](</docs/doc%20a>)
|
||||
[doc b](/docs/my%20docs/doc%20b)
|
||||
[doc b](</docs/my%20docs/doc%20b>)
|
||||
[doc]: </docs/my%20docs/doc%20b>
|
||||
",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`replaceMarkdownLinks replaces links with same title as URL 1`] = `
|
||||
{
|
||||
"brokenMarkdownLinks": [],
|
||||
"newContent": "
|
||||
[foo.md](/docs/foo)
|
||||
[./foo.md](</docs/foo>)
|
||||
[./foo.md](/docs/foo)
|
||||
[foo.md](/docs/foo)
|
||||
[./foo.md](/docs/foo)
|
||||
",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`replaceMarkdownLinks replaces multiple links on same line 1`] = `
|
||||
{
|
||||
"brokenMarkdownLinks": [],
|
||||
"newContent": "
|
||||
[a](/docs/a), [a](/docs/a), [b](/docs/b), [c](/docs/c)
|
||||
",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`replaceMarkdownLinks replaces reference style Markdown links 1`] = `
|
||||
{
|
||||
"brokenMarkdownLinks": [],
|
||||
"newContent": "
|
||||
The following operations are defined for [URI]s:
|
||||
|
||||
* [info]: Returns metadata about the resource,
|
||||
* [list]: Returns metadata about the resource's children (like getting the content of a local directory).
|
||||
|
||||
[URI]: /docs/api/classes/uri
|
||||
[info]: /docs/api/classes/uri#info
|
||||
[list]: /docs/api/classes/uri#list
|
||||
",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`replaceMarkdownLinks replaces two links on the same line 1`] = `
|
||||
{
|
||||
"brokenMarkdownLinks": [],
|
||||
"newContent": "[TypeScript](/programming-languages/typescript/) and [Go](/programming-languages/go/)",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`replaceMarkdownLinks resolves absolute and relative links differently 1`] = `
|
||||
{
|
||||
"brokenMarkdownLinks": [
|
||||
{
|
||||
"contentPaths": {
|
||||
"contentPath": "docs",
|
||||
"contentPathLocalized": "i18n/docs-localized",
|
||||
},
|
||||
"filePath": "docs/intro/intro.md",
|
||||
"link": "./api/classes/divine_uri.URI.md",
|
||||
},
|
||||
{
|
||||
"contentPaths": {
|
||||
"contentPath": "docs",
|
||||
"contentPathLocalized": "i18n/docs-localized",
|
||||
},
|
||||
"filePath": "docs/intro/intro.md",
|
||||
"link": "/another.md",
|
||||
},
|
||||
],
|
||||
"newContent": "
|
||||
[Relative link](/docs/another)
|
||||
[Relative link 2](/docs/api/classes/uri)
|
||||
[Relative link that should be absolute](./api/classes/divine_uri.URI.md)
|
||||
[Absolute link](/docs/api/classes/uri)
|
||||
[Absolute link from site dir](/docs/api/classes/uri)
|
||||
[Absolute link that should be relative](/another.md)
|
||||
[Relative link that acts as absolute](/docs/api/classes/uri)
|
||||
[Relative link that acts as relative](/docs/another)
|
||||
",
|
||||
}
|
||||
`;
|
|
@ -5,401 +5,70 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {replaceMarkdownLinks} from '../markdownLinks';
|
||||
import {resolveMarkdownLinkPathname} from '../markdownLinks';
|
||||
|
||||
describe('resolveMarkdownLinkPathname', () => {
|
||||
type Context = Parameters<typeof resolveMarkdownLinkPathname>[1];
|
||||
|
||||
describe('replaceMarkdownLinks', () => {
|
||||
it('does basic replace', () => {
|
||||
expect(
|
||||
replaceMarkdownLinks({
|
||||
siteDir: '.',
|
||||
filePath: 'docs/intro.md',
|
||||
contentPaths: {
|
||||
contentPath: 'docs',
|
||||
contentPathLocalized: 'i18n/docs-localized',
|
||||
},
|
||||
sourceToPermalink: {
|
||||
'@site/docs/intro.md': '/docs/intro',
|
||||
'@site/docs/foo.md': '/doc/foo',
|
||||
'@site/docs/bar/baz.md': '/doc/baz',
|
||||
'@site/docs/http.foo.md': '/doc/http',
|
||||
},
|
||||
fileString: `
|
||||
[foo](./foo.md)
|
||||
[baz](./bar/baz.md)
|
||||
[foo](foo.md)
|
||||
[http](http://github.com/facebook/docusaurus/README.md)
|
||||
[https](https://github.com/facebook/docusaurus/README.md)
|
||||
[asset](./foo.js)
|
||||
[asset as well](@site/docs/_partial.md)
|
||||
[looks like http...](http.foo.md)
|
||||
[nonexistent](hmmm.md)
|
||||
`,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
const context: Context = {
|
||||
siteDir: '.',
|
||||
sourceFilePath: 'docs/intro.md',
|
||||
contentPaths: {
|
||||
contentPath: 'docs',
|
||||
contentPathLocalized: 'i18n/docs-localized',
|
||||
},
|
||||
sourceToPermalink: {
|
||||
'@site/docs/intro.md': '/docs/intro',
|
||||
'@site/docs/foo.md': '/doc/foo',
|
||||
'@site/docs/bar/baz.md': '/doc/baz',
|
||||
'@site/docs/http.foo.md': '/doc/http',
|
||||
},
|
||||
};
|
||||
|
||||
it('replaces two links on the same line', () => {
|
||||
// cSpell:ignore Goooooooooo
|
||||
// This is a very arcane bug: if we continue matching using the previous
|
||||
// matching index (as is the behavior of RegExp#exec), it will go right over
|
||||
// the next Markdown link and fail to match the "Go" link. This only happens
|
||||
// when: (1) the replaced link is much shorter than the Markdown path, (2)
|
||||
// the next link is very close to the current one (e.g. here if it's not
|
||||
// "Go" but "Goooooooooo", or if every link has the /docs/ prefix, the bug
|
||||
// will not trigger because it won't overshoot)
|
||||
expect(
|
||||
replaceMarkdownLinks({
|
||||
siteDir: '.',
|
||||
filePath: 'docs/intro.md',
|
||||
contentPaths: {
|
||||
contentPath: 'docs',
|
||||
contentPathLocalized: 'i18n/docs-localized',
|
||||
},
|
||||
sourceToPermalink: {
|
||||
'@site/docs/intro.md': '/',
|
||||
'@site/docs/programming-languages/typescript/typescript.md':
|
||||
'/programming-languages/typescript/',
|
||||
'@site/docs/programming-languages/go/go.md':
|
||||
'/programming-languages/go/',
|
||||
},
|
||||
fileString: `[TypeScript](programming-languages/typescript/typescript.md) and [Go](programming-languages/go/go.md)`,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
function test(linkPathname: string, expectedOutput: string) {
|
||||
const output = resolveMarkdownLinkPathname(linkPathname, context);
|
||||
expect(output).toEqual(expectedOutput);
|
||||
}
|
||||
|
||||
it('replaces reference style Markdown links', () => {
|
||||
expect(
|
||||
replaceMarkdownLinks({
|
||||
siteDir: '.',
|
||||
filePath: 'docs/intro/intro.md',
|
||||
contentPaths: {
|
||||
contentPath: 'docs',
|
||||
contentPathLocalized: 'i18n/docs-localized',
|
||||
},
|
||||
|
||||
sourceToPermalink: {
|
||||
'@site/docs/intro/intro.md': '/docs/intro',
|
||||
'@site/docs/api/classes/divine_uri.URI.md': '/docs/api/classes/uri',
|
||||
},
|
||||
|
||||
fileString: `
|
||||
The following operations are defined for [URI]s:
|
||||
|
||||
* [info]: Returns metadata about the resource,
|
||||
* [list]: Returns metadata about the resource's children (like getting the content of a local directory).
|
||||
|
||||
[URI]: ../api/classes/divine_uri.URI.md
|
||||
[info]: ../api/classes/divine_uri.URI.md#info
|
||||
[list]: ../api/classes/divine_uri.URI.md#list
|
||||
`,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
test('./foo.md', '/doc/foo');
|
||||
test('foo.md', '/doc/foo');
|
||||
test('./bar/baz.md', '/doc/baz');
|
||||
test('http.foo.md', '/doc/http');
|
||||
test('@site/docs/_partial.md', null);
|
||||
test('foo.js', null);
|
||||
test('nonexistent.md', null);
|
||||
test('https://github.com/facebook/docusaurus/README.md', null);
|
||||
});
|
||||
|
||||
it('resolves absolute and relative links differently', () => {
|
||||
expect(
|
||||
replaceMarkdownLinks({
|
||||
siteDir: '.',
|
||||
filePath: 'docs/intro/intro.md',
|
||||
contentPaths: {
|
||||
contentPath: 'docs',
|
||||
contentPathLocalized: 'i18n/docs-localized',
|
||||
},
|
||||
const context: Context = {
|
||||
siteDir: '.',
|
||||
sourceFilePath: 'docs/intro/intro.md',
|
||||
contentPaths: {
|
||||
contentPath: 'docs',
|
||||
contentPathLocalized: 'i18n/docs-localized',
|
||||
},
|
||||
|
||||
sourceToPermalink: {
|
||||
'@site/docs/intro/intro.md': '/docs/intro',
|
||||
'@site/docs/intro/another.md': '/docs/another',
|
||||
'@site/docs/api/classes/divine_uri.URI.md': '/docs/api/classes/uri',
|
||||
},
|
||||
sourceToPermalink: {
|
||||
'@site/docs/intro/intro.md': '/docs/intro',
|
||||
'@site/docs/intro/another.md': '/docs/another',
|
||||
'@site/docs/api/classes/divine_uri.URI.md': '/docs/api/classes/uri',
|
||||
},
|
||||
};
|
||||
|
||||
fileString: `
|
||||
[Relative link](./another.md)
|
||||
[Relative link 2](../api/classes/divine_uri.URI.md)
|
||||
[Relative link that should be absolute](./api/classes/divine_uri.URI.md)
|
||||
[Absolute link](/api/classes/divine_uri.URI.md)
|
||||
[Absolute link from site dir](/docs/api/classes/divine_uri.URI.md)
|
||||
[Absolute link that should be relative](/another.md)
|
||||
[Relative link that acts as absolute](api/classes/divine_uri.URI.md)
|
||||
[Relative link that acts as relative](another.md)
|
||||
`,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
function test(linkPathname: string, expectedOutput: string) {
|
||||
const output = resolveMarkdownLinkPathname(linkPathname, context);
|
||||
expect(output).toEqual(expectedOutput);
|
||||
}
|
||||
|
||||
// TODO bad
|
||||
it('ignores links in HTML comments', () => {
|
||||
expect(
|
||||
replaceMarkdownLinks({
|
||||
siteDir: '.',
|
||||
filePath: 'docs/intro.md',
|
||||
contentPaths: {
|
||||
contentPath: 'docs',
|
||||
contentPathLocalized: 'i18n/docs-localized',
|
||||
},
|
||||
sourceToPermalink: {
|
||||
'@site/docs/intro.md': '/docs/intro',
|
||||
},
|
||||
fileString: `
|
||||
<!-- [foo](./foo.md) -->
|
||||
<!--
|
||||
[foo](./foo.md)
|
||||
-->
|
||||
`,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('ignores links in fenced blocks', () => {
|
||||
expect(
|
||||
replaceMarkdownLinks({
|
||||
siteDir: '.',
|
||||
filePath: 'docs/intro.md',
|
||||
contentPaths: {
|
||||
contentPath: 'docs',
|
||||
contentPathLocalized: 'i18n/docs-localized',
|
||||
},
|
||||
sourceToPermalink: {
|
||||
'@site/docs/intro.md': '/docs/intro',
|
||||
},
|
||||
fileString: `
|
||||
\`\`\`
|
||||
[foo](foo.md)
|
||||
\`\`\`
|
||||
|
||||
\`\`\`\`js
|
||||
[foo](foo.md)
|
||||
\`\`\`
|
||||
[foo](foo.md)
|
||||
\`\`\`
|
||||
[foo](foo.md)
|
||||
\`\`\`\`
|
||||
|
||||
\`\`\`\`js
|
||||
[foo](foo.md)
|
||||
\`\`\`
|
||||
[foo](foo.md)
|
||||
\`\`\`\`
|
||||
|
||||
~~~js
|
||||
[foo](foo.md)
|
||||
~~~
|
||||
|
||||
~~~js
|
||||
[foo](foo.md)
|
||||
\`\`\`
|
||||
[foo](foo.md)
|
||||
\`\`\`
|
||||
[foo](foo.md)
|
||||
~~~
|
||||
`,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
// FIXME
|
||||
it('ignores links in inline code', () => {
|
||||
expect(
|
||||
replaceMarkdownLinks({
|
||||
siteDir: '.',
|
||||
filePath: 'docs/intro.md',
|
||||
contentPaths: {
|
||||
contentPath: 'docs',
|
||||
contentPathLocalized: 'i18n/docs-localized',
|
||||
},
|
||||
sourceToPermalink: {
|
||||
'@site/docs/intro.md': '/docs/intro',
|
||||
},
|
||||
fileString: `
|
||||
\`[foo](foo.md)\`
|
||||
`,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('replaces links with same title as URL', () => {
|
||||
expect(
|
||||
replaceMarkdownLinks({
|
||||
siteDir: '.',
|
||||
filePath: 'docs/intro.md',
|
||||
contentPaths: {
|
||||
contentPath: 'docs',
|
||||
contentPathLocalized: 'i18n/docs-localized',
|
||||
},
|
||||
sourceToPermalink: {
|
||||
'@site/docs/intro.md': '/docs/intro',
|
||||
'@site/docs/foo.md': '/docs/foo',
|
||||
},
|
||||
fileString: `
|
||||
[foo.md](foo.md)
|
||||
[./foo.md](<./foo.md>)
|
||||
[./foo.md](./foo.md)
|
||||
[foo.md](./foo.md)
|
||||
[./foo.md](foo.md)
|
||||
`,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('replaces multiple links on same line', () => {
|
||||
expect(
|
||||
replaceMarkdownLinks({
|
||||
siteDir: '.',
|
||||
filePath: 'docs/intro.md',
|
||||
contentPaths: {
|
||||
contentPath: 'docs',
|
||||
contentPathLocalized: 'i18n/docs-localized',
|
||||
},
|
||||
sourceToPermalink: {
|
||||
'@site/docs/intro.md': '/docs/intro',
|
||||
'@site/docs/a.md': '/docs/a',
|
||||
'@site/docs/b.md': '/docs/b',
|
||||
'@site/docs/c.md': '/docs/c',
|
||||
},
|
||||
fileString: `
|
||||
[a](a.md), [a](a.md), [b](b.md), [c](c.md)
|
||||
`,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('replaces Markdown links with spaces', () => {
|
||||
expect(
|
||||
replaceMarkdownLinks({
|
||||
siteDir: '.',
|
||||
filePath: 'docs/intro.md',
|
||||
contentPaths: {
|
||||
contentPath: 'docs',
|
||||
contentPathLocalized: 'i18n/docs-localized',
|
||||
},
|
||||
sourceToPermalink: {
|
||||
'@site/docs/doc a.md': '/docs/doc%20a',
|
||||
'@site/docs/my docs/doc b.md': '/docs/my%20docs/doc%20b',
|
||||
},
|
||||
fileString: `
|
||||
[doc a](./doc%20a.md)
|
||||
[doc a](<./doc a.md>)
|
||||
[doc b](./my%20docs/doc%20b.md)
|
||||
[doc b](<./my docs/doc b.md>)
|
||||
[doc]: <./my docs/doc b.md>
|
||||
`,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('does not replace non-Markdown links', () => {
|
||||
const input = `
|
||||
[asset](./file.md_asset/1.png)
|
||||
[URL](<https://example.com/file_(1).md>)
|
||||
[not a link]((foo)
|
||||
[not a link](foo bar)
|
||||
[not a link]: foo bar
|
||||
[not a link]: (foo
|
||||
[not a link]: bar)
|
||||
`;
|
||||
expect(
|
||||
replaceMarkdownLinks({
|
||||
siteDir: '.',
|
||||
filePath: 'docs/file.md',
|
||||
contentPaths: {
|
||||
contentPath: 'docs',
|
||||
contentPathLocalized: 'i18n/docs-localized',
|
||||
},
|
||||
sourceToPermalink: {
|
||||
'@site/docs/file.md': '/docs/file',
|
||||
},
|
||||
fileString: input,
|
||||
}),
|
||||
).toEqual({
|
||||
newContent: input,
|
||||
brokenMarkdownLinks: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('handles stray spaces', () => {
|
||||
expect(
|
||||
replaceMarkdownLinks({
|
||||
siteDir: '.',
|
||||
filePath: 'docs/file.md',
|
||||
contentPaths: {
|
||||
contentPath: 'docs',
|
||||
contentPathLocalized: 'i18n/docs-localized',
|
||||
},
|
||||
sourceToPermalink: {
|
||||
'@site/docs/file.md': '/docs/file',
|
||||
},
|
||||
fileString: `
|
||||
[URL]( ./file.md )
|
||||
[ref]: ./file.md
|
||||
`,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('handles link titles', () => {
|
||||
expect(
|
||||
replaceMarkdownLinks({
|
||||
siteDir: '.',
|
||||
filePath: 'docs/file.md',
|
||||
contentPaths: {
|
||||
contentPath: 'docs',
|
||||
contentPathLocalized: 'i18n/docs-localized',
|
||||
},
|
||||
sourceToPermalink: {
|
||||
'@site/docs/file.md': '/docs/file',
|
||||
},
|
||||
fileString: `
|
||||
[URL](./file.md "title")
|
||||
[URL](./file.md 'title')
|
||||
[URL](./file.md (title))
|
||||
`,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('preserves query/hash', () => {
|
||||
expect(
|
||||
replaceMarkdownLinks({
|
||||
siteDir: '.',
|
||||
filePath: 'docs/file.md',
|
||||
contentPaths: {
|
||||
contentPath: 'docs',
|
||||
contentPathLocalized: 'i18n/docs-localized',
|
||||
},
|
||||
sourceToPermalink: {
|
||||
'@site/docs/file.md': '/docs/file',
|
||||
},
|
||||
fileString: `
|
||||
[URL](./file.md?foo=bar#baz)
|
||||
[URL](./file.md#a)
|
||||
[URL](./file.md?c)
|
||||
`,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('handles unpaired fences', () => {
|
||||
expect(
|
||||
replaceMarkdownLinks({
|
||||
siteDir: '.',
|
||||
filePath: 'docs/file.md',
|
||||
contentPaths: {
|
||||
contentPath: 'docs',
|
||||
contentPathLocalized: 'i18n/docs-localized',
|
||||
},
|
||||
sourceToPermalink: {
|
||||
'@site/docs/file.md': '/docs/file',
|
||||
},
|
||||
fileString: `
|
||||
\`\`\`foo
|
||||
hello
|
||||
|
||||
\`\`\`foo
|
||||
hello
|
||||
\`\`\`
|
||||
|
||||
A [link](./file.md)
|
||||
`,
|
||||
}),
|
||||
).toMatchSnapshot();
|
||||
test('./another.md', '/docs/another');
|
||||
test('../api/classes/divine_uri.URI.md', '/docs/api/classes/uri');
|
||||
test('./api/classes/divine_uri.URI.md', null);
|
||||
test('/api/classes/divine_uri.URI.md', '/docs/api/classes/uri');
|
||||
test('/docs/api/classes/divine_uri.URI.md', '/docs/api/classes/uri');
|
||||
test('/another.md', null);
|
||||
test('api/classes/divine_uri.URI.md', '/docs/api/classes/uri');
|
||||
test('another.md', '/docs/another');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,6 +17,9 @@ import {
|
|||
hasSSHProtocol,
|
||||
parseURLPath,
|
||||
serializeURLPath,
|
||||
parseURLOrPath,
|
||||
toURLPath,
|
||||
parseLocalURLPath,
|
||||
} from '../urlUtils';
|
||||
|
||||
describe('normalizeUrl', () => {
|
||||
|
@ -228,6 +231,166 @@ describe('isValidPathname', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('toURLPath', () => {
|
||||
it('url', () => {
|
||||
const url = new URL('https://example.com/pathname?qs#hash');
|
||||
expect(toURLPath(url)).toEqual({
|
||||
pathname: '/pathname',
|
||||
search: 'qs',
|
||||
hash: 'hash',
|
||||
});
|
||||
});
|
||||
|
||||
it('pathname + qs', () => {
|
||||
const url = parseURLOrPath('/pathname?qs');
|
||||
expect(toURLPath(url)).toEqual({
|
||||
pathname: '/pathname',
|
||||
search: 'qs',
|
||||
hash: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('pathname + hash', () => {
|
||||
const url = parseURLOrPath('/pathname#hash');
|
||||
expect(toURLPath(url)).toEqual({
|
||||
pathname: '/pathname',
|
||||
search: undefined,
|
||||
hash: 'hash',
|
||||
});
|
||||
});
|
||||
|
||||
it('pathname + qs + hash', () => {
|
||||
const url = parseURLOrPath('/pathname?qs#hash');
|
||||
expect(toURLPath(url)).toEqual({
|
||||
pathname: '/pathname',
|
||||
search: 'qs',
|
||||
hash: 'hash',
|
||||
});
|
||||
});
|
||||
|
||||
it('pathname + empty qs + empty hash', () => {
|
||||
const url = parseURLOrPath('/pathname?#');
|
||||
expect(toURLPath(url)).toEqual({
|
||||
pathname: '/pathname',
|
||||
search: '',
|
||||
hash: '',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseLocalURLPath', () => {
|
||||
it('returns null for non-local URLs', () => {
|
||||
expect(parseLocalURLPath('https://example')).toBeNull();
|
||||
expect(parseLocalURLPath('https://example:80')).toBeNull();
|
||||
expect(parseLocalURLPath('https://example.com/xyz')).toBeNull();
|
||||
expect(parseLocalURLPath('https://example.com/xyz?qs#hash')).toBeNull();
|
||||
expect(parseLocalURLPath('https://example.com:80/xyz?qs#hash')).toBeNull();
|
||||
expect(parseLocalURLPath('https://u:p@example:80/xyz?qs#hash')).toBeNull();
|
||||
});
|
||||
|
||||
it('parses pathname', () => {
|
||||
expect(parseLocalURLPath('/pathname')).toEqual({
|
||||
pathname: '/pathname',
|
||||
search: undefined,
|
||||
hash: undefined,
|
||||
});
|
||||
expect(parseLocalURLPath('pathname.md')).toEqual({
|
||||
pathname: 'pathname.md',
|
||||
search: undefined,
|
||||
hash: undefined,
|
||||
});
|
||||
expect(parseLocalURLPath('./pathname')).toEqual({
|
||||
pathname: './pathname',
|
||||
search: undefined,
|
||||
hash: undefined,
|
||||
});
|
||||
expect(parseLocalURLPath('../../pathname.mdx')).toEqual({
|
||||
pathname: '../../pathname.mdx',
|
||||
search: undefined,
|
||||
hash: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('parses qs', () => {
|
||||
expect(parseLocalURLPath('?')).toEqual({
|
||||
pathname: '',
|
||||
search: '',
|
||||
hash: undefined,
|
||||
});
|
||||
expect(parseLocalURLPath('?qs')).toEqual({
|
||||
pathname: '',
|
||||
search: 'qs',
|
||||
hash: undefined,
|
||||
});
|
||||
expect(parseLocalURLPath('?age=42')).toEqual({
|
||||
pathname: '',
|
||||
search: 'age=42',
|
||||
hash: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('parses hash', () => {
|
||||
expect(parseLocalURLPath('#')).toEqual({
|
||||
pathname: '',
|
||||
search: undefined,
|
||||
hash: '',
|
||||
});
|
||||
expect(parseLocalURLPath('#hash')).toEqual({
|
||||
pathname: '',
|
||||
search: undefined,
|
||||
hash: 'hash',
|
||||
});
|
||||
});
|
||||
|
||||
it('parses complex local paths', () => {
|
||||
expect(
|
||||
parseLocalURLPath('../../great/path name/doc.mdx?age=42#hash'),
|
||||
).toEqual({
|
||||
pathname: '../../great/path name/doc.mdx',
|
||||
search: 'age=42',
|
||||
hash: 'hash',
|
||||
});
|
||||
expect(parseLocalURLPath('my great path?=42#hash?qsInHash')).toEqual({
|
||||
pathname: 'my great path',
|
||||
search: '=42',
|
||||
hash: 'hash?qsInHash',
|
||||
});
|
||||
expect(parseLocalURLPath('?qs1#hash1?qs2#hash2')).toEqual({
|
||||
pathname: '',
|
||||
search: 'qs1',
|
||||
hash: 'hash1?qs2#hash2',
|
||||
});
|
||||
expect(parseLocalURLPath('../swizzling.mdx#wrapping')).toEqual({
|
||||
pathname: '../swizzling.mdx',
|
||||
search: undefined,
|
||||
hash: 'wrapping',
|
||||
});
|
||||
});
|
||||
|
||||
it('parses is isomorphic with serialize', () => {
|
||||
const testLocalPath = (url: string) => {
|
||||
expect(serializeURLPath(parseLocalURLPath(url)!)).toBe(url);
|
||||
};
|
||||
[
|
||||
'',
|
||||
'doc',
|
||||
'doc.mdx',
|
||||
'./doc.mdx',
|
||||
'.././doc.mdx',
|
||||
'/some pathname/.././doc.mdx',
|
||||
'?',
|
||||
'?qs',
|
||||
'#',
|
||||
'#hash',
|
||||
'?qs#hash',
|
||||
'?qs#hash',
|
||||
'doc.mdx?qs#hash',
|
||||
'/some pathname/.././doc.mdx?qs#hash',
|
||||
'/some pathname/.././doc.mdx?qs#hash?qs2#hash2',
|
||||
].forEach(testLocalPath);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseURLPath', () => {
|
||||
it('parse and resolve pathname', () => {
|
||||
expect(parseURLPath('')).toEqual({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue