mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 07:37:19 +02:00
feat(v2): absolute slugs and slug resolution system (#3084)
* rework slug to allow absolute slugs and slug resolution * add slug metadata tests * refactor docs metadata test + fix slug bugs * fix tests * fix docs tests failing due to randomness + update snapshot * add test for addLeadingSlash
This commit is contained in:
parent
6730590c1e
commit
f4434b2e42
39 changed files with 791 additions and 255 deletions
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
slug: hello/super
|
|
||||||
---
|
|
||||||
|
|
||||||
Lorem
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: /rootAbsoluteSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: rootRelativeSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: ./hey/ho/../rootResolvedSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: ../../../../../../../../rootTryToEscapeSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: /absoluteSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: relativeSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: ./hey/ho/../resolvedSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: ../../../../../../../../tryToEscapeSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: /absoluteSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: relativeSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: ./hey/ho/../resolvedSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: ../../../../../../../../tryToEscapeSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: /rootAbsoluteSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: rootRelativeSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: ./hey/ho/../rootResolvedSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: ../../../../../../../../rootTryToEscapeSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: /absoluteSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: relativeSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: ./hey/ho/../resolvedSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
slug: ../../../../../../../../tryToEscapeSlug
|
||||||
|
---
|
||||||
|
|
||||||
|
Lorem
|
|
@ -1,4 +1,5 @@
|
||||||
[
|
[
|
||||||
"1.0.1",
|
"1.0.1",
|
||||||
"1.0.0"
|
"1.0.0",
|
||||||
|
"withSlugs"
|
||||||
]
|
]
|
||||||
|
|
|
@ -72,6 +72,14 @@ Array [
|
||||||
},
|
},
|
||||||
"path": "/docs/",
|
"path": "/docs/",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/docs/slugs/absoluteSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/absoluteSlug",
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"component": "@theme/DocItem",
|
"component": "@theme/DocItem",
|
||||||
"exact": true,
|
"exact": true,
|
||||||
|
@ -88,6 +96,14 @@ Array [
|
||||||
},
|
},
|
||||||
"path": "/docs/foo/bazSlug.html",
|
"path": "/docs/foo/bazSlug.html",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/docs/rootResolvedSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/hey/rootResolvedSlug",
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"component": "@theme/DocItem",
|
"component": "@theme/DocItem",
|
||||||
"exact": true,
|
"exact": true,
|
||||||
|
@ -104,6 +120,54 @@ Array [
|
||||||
},
|
},
|
||||||
"path": "/docs/lorem",
|
"path": "/docs/lorem",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/docs/rootAbsoluteSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/rootAbsoluteSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/docs/rootRelativeSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/rootRelativeSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/docs/rootTryToEscapeSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/rootTryToEscapeSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/docs/slugs/resolvedSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/slugs/hey/resolvedSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/docs/slugs/relativeSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/slugs/relativeSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/docs/slugs/tryToEscapeSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/tryToEscapeSlug",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -138,6 +202,38 @@ Object {
|
||||||
"id": "lorem",
|
"id": "lorem",
|
||||||
"path": "/docs/lorem",
|
"path": "/docs/lorem",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"id": "rootAbsoluteSlug",
|
||||||
|
"path": "/docs/rootAbsoluteSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "rootRelativeSlug",
|
||||||
|
"path": "/docs/rootRelativeSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "rootResolvedSlug",
|
||||||
|
"path": "/docs/hey/rootResolvedSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "rootTryToEscapeSlug",
|
||||||
|
"path": "/docs/rootTryToEscapeSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "slugs/absoluteSlug",
|
||||||
|
"path": "/docs/absoluteSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "slugs/relativeSlug",
|
||||||
|
"path": "/docs/slugs/relativeSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "slugs/resolvedSlug",
|
||||||
|
"path": "/docs/slugs/hey/resolvedSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "slugs/tryToEscapeSlug",
|
||||||
|
"path": "/docs/tryToEscapeSlug",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"mainDocId": "hello",
|
"mainDocId": "hello",
|
||||||
"name": null,
|
"name": null,
|
||||||
|
@ -156,7 +252,15 @@ Available document ids=
|
||||||
- foo/baz
|
- foo/baz
|
||||||
- hello
|
- hello
|
||||||
- ipsum
|
- ipsum
|
||||||
- lorem"
|
- lorem
|
||||||
|
- rootAbsoluteSlug
|
||||||
|
- rootRelativeSlug
|
||||||
|
- rootResolvedSlug
|
||||||
|
- rootTryToEscapeSlug
|
||||||
|
- slugs/absoluteSlug
|
||||||
|
- slugs/relativeSlug
|
||||||
|
- slugs/resolvedSlug
|
||||||
|
- slugs/tryToEscapeSlug"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`versioned website content 1`] = `
|
exports[`versioned website content 1`] = `
|
||||||
|
@ -213,6 +317,14 @@ Array [
|
||||||
},
|
},
|
||||||
"path": "/docs/next/",
|
"path": "/docs/next/",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/docs/slugs/absoluteSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/next/absoluteSlug",
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"component": "@theme/DocItem",
|
"component": "@theme/DocItem",
|
||||||
"exact": true,
|
"exact": true,
|
||||||
|
@ -221,6 +333,105 @@ Array [
|
||||||
},
|
},
|
||||||
"path": "/docs/next/foo/barSlug",
|
"path": "/docs/next/foo/barSlug",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/docs/slugs/resolvedSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/next/slugs/hey/resolvedSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/docs/slugs/relativeSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/next/slugs/relativeSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/docs/slugs/tryToEscapeSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/next/tryToEscapeSlug",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocPage",
|
||||||
|
"exact": false,
|
||||||
|
"modules": Object {
|
||||||
|
"docsMetadata": "~docs/docs-with-slugs-route-335.json",
|
||||||
|
},
|
||||||
|
"path": "/docs/withSlugs",
|
||||||
|
"priority": undefined,
|
||||||
|
"routes": Array [
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/versioned_docs/version-withSlugs/slugs/absoluteSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/withSlugs/absoluteSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/versioned_docs/version-withSlugs/rootResolvedSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/withSlugs/hey/rootResolvedSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/versioned_docs/version-withSlugs/rootAbsoluteSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/withSlugs/rootAbsoluteSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/versioned_docs/version-withSlugs/rootRelativeSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/withSlugs/rootRelativeSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/versioned_docs/version-withSlugs/rootTryToEscapeSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/withSlugs/rootTryToEscapeSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/versioned_docs/version-withSlugs/slugs/resolvedSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/withSlugs/slugs/hey/resolvedSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/versioned_docs/version-withSlugs/slugs/relativeSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/withSlugs/slugs/relativeSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"component": "@theme/DocItem",
|
||||||
|
"exact": true,
|
||||||
|
"modules": Object {
|
||||||
|
"content": "@site/versioned_docs/version-withSlugs/slugs/tryToEscapeSlug.md",
|
||||||
|
},
|
||||||
|
"path": "/docs/withSlugs/tryToEscapeSlug",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
|
@ -270,6 +481,22 @@ Object {
|
||||||
"id": "hello",
|
"id": "hello",
|
||||||
"path": "/docs/next/",
|
"path": "/docs/next/",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"id": "slugs/absoluteSlug",
|
||||||
|
"path": "/docs/next/absoluteSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "slugs/relativeSlug",
|
||||||
|
"path": "/docs/next/slugs/relativeSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "slugs/resolvedSlug",
|
||||||
|
"path": "/docs/next/slugs/hey/resolvedSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "slugs/tryToEscapeSlug",
|
||||||
|
"path": "/docs/next/tryToEscapeSlug",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
"mainDocId": "hello",
|
"mainDocId": "hello",
|
||||||
"name": "next",
|
"name": "next",
|
||||||
|
@ -309,6 +536,45 @@ Object {
|
||||||
"name": "1.0.0",
|
"name": "1.0.0",
|
||||||
"path": "/docs/1.0.0",
|
"path": "/docs/1.0.0",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"docs": Array [
|
||||||
|
Object {
|
||||||
|
"id": "rootAbsoluteSlug",
|
||||||
|
"path": "/docs/withSlugs/rootAbsoluteSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "rootRelativeSlug",
|
||||||
|
"path": "/docs/withSlugs/rootRelativeSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "rootResolvedSlug",
|
||||||
|
"path": "/docs/withSlugs/hey/rootResolvedSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "rootTryToEscapeSlug",
|
||||||
|
"path": "/docs/withSlugs/rootTryToEscapeSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "slugs/absoluteSlug",
|
||||||
|
"path": "/docs/withSlugs/absoluteSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "slugs/relativeSlug",
|
||||||
|
"path": "/docs/withSlugs/slugs/relativeSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "slugs/resolvedSlug",
|
||||||
|
"path": "/docs/withSlugs/slugs/hey/resolvedSlug",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "slugs/tryToEscapeSlug",
|
||||||
|
"path": "/docs/withSlugs/tryToEscapeSlug",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"mainDocId": "rootAbsoluteSlug",
|
||||||
|
"name": "withSlugs",
|
||||||
|
"path": "/docs/withSlugs",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,7 +21,11 @@ describe('loadEnv', () => {
|
||||||
const env = loadEnv(siteDir);
|
const env = loadEnv(siteDir);
|
||||||
expect(env.versioning.enabled).toBe(true);
|
expect(env.versioning.enabled).toBe(true);
|
||||||
expect(env.versioning.latestVersion).toBe('1.0.1');
|
expect(env.versioning.latestVersion).toBe('1.0.1');
|
||||||
expect(env.versioning.versions).toStrictEqual(['1.0.1', '1.0.0']);
|
expect(env.versioning.versions).toStrictEqual([
|
||||||
|
'1.0.1',
|
||||||
|
'1.0.0',
|
||||||
|
'withSlugs',
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('website with versioning but disabled', () => {
|
test('website with versioning but disabled', () => {
|
||||||
|
|
|
@ -269,8 +269,10 @@ describe('versioned website', () => {
|
||||||
"docs/**/*.{md,mdx}",
|
"docs/**/*.{md,mdx}",
|
||||||
"versioned_sidebars/version-1.0.1-sidebars.json",
|
"versioned_sidebars/version-1.0.1-sidebars.json",
|
||||||
"versioned_sidebars/version-1.0.0-sidebars.json",
|
"versioned_sidebars/version-1.0.0-sidebars.json",
|
||||||
|
"versioned_sidebars/version-withSlugs-sidebars.json",
|
||||||
"versioned_docs/version-1.0.1/**/*.{md,mdx}",
|
"versioned_docs/version-1.0.1/**/*.{md,mdx}",
|
||||||
"versioned_docs/version-1.0.0/**/*.{md,mdx}",
|
"versioned_docs/version-1.0.0/**/*.{md,mdx}",
|
||||||
|
"versioned_docs/version-withSlugs/**/*.{md,mdx}",
|
||||||
"sidebars.json",
|
"sidebars.json",
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
|
|
|
@ -9,136 +9,150 @@ import path from 'path';
|
||||||
import {loadContext} from '@docusaurus/core/src/server/index';
|
import {loadContext} from '@docusaurus/core/src/server/index';
|
||||||
import processMetadata from '../metadata';
|
import processMetadata from '../metadata';
|
||||||
import loadEnv from '../env';
|
import loadEnv from '../env';
|
||||||
|
import {MetadataRaw, Env, MetadataOptions} from '../types';
|
||||||
|
import {LoadContext} from '@docusaurus/types';
|
||||||
|
|
||||||
const fixtureDir = path.join(__dirname, '__fixtures__');
|
const fixtureDir = path.join(__dirname, '__fixtures__');
|
||||||
|
|
||||||
describe('simple site', () => {
|
function createTestHelpers({
|
||||||
const simpleSiteDir = path.join(fixtureDir, 'simple-site');
|
siteDir,
|
||||||
const context = loadContext(simpleSiteDir);
|
context,
|
||||||
const routeBasePath = 'docs';
|
env,
|
||||||
const docsDir = path.resolve(simpleSiteDir, routeBasePath);
|
options,
|
||||||
|
}: {
|
||||||
|
siteDir: string;
|
||||||
|
context: LoadContext;
|
||||||
|
env: Env;
|
||||||
|
options: MetadataOptions;
|
||||||
|
}) {
|
||||||
|
async function testMeta(
|
||||||
|
refDir: string,
|
||||||
|
source: string,
|
||||||
|
expectedMetadata: Omit<MetadataRaw, 'source'>,
|
||||||
|
) {
|
||||||
|
const metadata = await processMetadata({
|
||||||
|
source,
|
||||||
|
refDir,
|
||||||
|
context,
|
||||||
|
options,
|
||||||
|
env,
|
||||||
|
});
|
||||||
|
expect(metadata).toEqual({
|
||||||
|
...expectedMetadata,
|
||||||
|
source: path.join('@site', path.relative(siteDir, refDir), source),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const env = loadEnv(simpleSiteDir);
|
async function testSlug(
|
||||||
|
refDir: string,
|
||||||
|
source: string,
|
||||||
|
expectedPermalink: string,
|
||||||
|
) {
|
||||||
|
const metadata = await processMetadata({
|
||||||
|
source,
|
||||||
|
refDir,
|
||||||
|
context,
|
||||||
|
options,
|
||||||
|
env,
|
||||||
|
});
|
||||||
|
expect(metadata.permalink).toEqual(expectedPermalink);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {testMeta, testSlug};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('simple site', () => {
|
||||||
|
const siteDir = path.join(fixtureDir, 'simple-site');
|
||||||
|
const context = loadContext(siteDir);
|
||||||
|
const routeBasePath = 'docs';
|
||||||
|
const docsDir = path.resolve(siteDir, routeBasePath);
|
||||||
|
const env = loadEnv(siteDir);
|
||||||
|
const options = {routeBasePath};
|
||||||
|
|
||||||
|
const {testMeta, testSlug} = createTestHelpers({
|
||||||
|
siteDir,
|
||||||
|
context,
|
||||||
|
options,
|
||||||
|
env,
|
||||||
|
});
|
||||||
|
|
||||||
test('normal docs', async () => {
|
test('normal docs', async () => {
|
||||||
const sourceA = path.join('foo', 'bar.md');
|
await testMeta(docsDir, path.join('foo', 'bar.md'), {
|
||||||
const sourceB = path.join('hello.md');
|
|
||||||
const options = {
|
|
||||||
routeBasePath,
|
|
||||||
};
|
|
||||||
|
|
||||||
const [dataA, dataB] = await Promise.all([
|
|
||||||
processMetadata({
|
|
||||||
source: sourceA,
|
|
||||||
refDir: docsDir,
|
|
||||||
context,
|
|
||||||
options,
|
|
||||||
env,
|
|
||||||
}),
|
|
||||||
processMetadata({
|
|
||||||
source: sourceB,
|
|
||||||
refDir: docsDir,
|
|
||||||
context,
|
|
||||||
options,
|
|
||||||
env,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(dataA).toEqual({
|
|
||||||
id: 'foo/bar',
|
id: 'foo/bar',
|
||||||
unversionedId: 'foo/bar',
|
unversionedId: 'foo/bar',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/foo/bar',
|
permalink: '/docs/foo/bar',
|
||||||
source: path.join('@site', routeBasePath, sourceA),
|
|
||||||
title: 'Bar',
|
title: 'Bar',
|
||||||
description: 'This is custom description',
|
description: 'This is custom description',
|
||||||
});
|
});
|
||||||
expect(dataB).toEqual({
|
await testMeta(docsDir, path.join('hello.md'), {
|
||||||
id: 'hello',
|
id: 'hello',
|
||||||
unversionedId: 'hello',
|
unversionedId: 'hello',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/hello',
|
permalink: '/docs/hello',
|
||||||
source: path.join('@site', routeBasePath, sourceB),
|
|
||||||
title: 'Hello, World !',
|
title: 'Hello, World !',
|
||||||
description: `Hi, Endilie here :)`,
|
description: `Hi, Endilie here :)`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('homePageId doc', async () => {
|
test('homePageId doc', async () => {
|
||||||
const source = path.join('hello.md');
|
const {testMeta: testMetaLocal} = createTestHelpers({
|
||||||
const options = {
|
siteDir,
|
||||||
routeBasePath,
|
options: {
|
||||||
homePageId: 'hello',
|
routeBasePath,
|
||||||
};
|
homePageId: 'hello',
|
||||||
|
},
|
||||||
const data = await processMetadata({
|
|
||||||
source,
|
|
||||||
refDir: docsDir,
|
|
||||||
context,
|
context,
|
||||||
options,
|
|
||||||
env,
|
env,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(data).toEqual({
|
await testMetaLocal(docsDir, path.join('hello.md'), {
|
||||||
id: 'hello',
|
id: 'hello',
|
||||||
unversionedId: 'hello',
|
unversionedId: 'hello',
|
||||||
isDocsHomePage: true,
|
isDocsHomePage: true,
|
||||||
permalink: '/docs/',
|
permalink: '/docs/',
|
||||||
source: path.join('@site', routeBasePath, source),
|
|
||||||
title: 'Hello, World !',
|
title: 'Hello, World !',
|
||||||
description: `Hi, Endilie here :)`,
|
description: `Hi, Endilie here :)`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('homePageId doc nested', async () => {
|
test('homePageId doc nested', async () => {
|
||||||
const source = path.join('foo', 'bar.md');
|
const {testMeta: testMetaLocal} = createTestHelpers({
|
||||||
const options = {
|
siteDir,
|
||||||
routeBasePath,
|
options: {
|
||||||
homePageId: 'foo/bar',
|
routeBasePath,
|
||||||
};
|
homePageId: 'foo/bar',
|
||||||
|
},
|
||||||
const data = await processMetadata({
|
|
||||||
source,
|
|
||||||
refDir: docsDir,
|
|
||||||
context,
|
context,
|
||||||
options,
|
|
||||||
env,
|
env,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(data).toEqual({
|
await testMetaLocal(docsDir, path.join('foo', 'bar.md'), {
|
||||||
id: 'foo/bar',
|
id: 'foo/bar',
|
||||||
unversionedId: 'foo/bar',
|
unversionedId: 'foo/bar',
|
||||||
isDocsHomePage: true,
|
isDocsHomePage: true,
|
||||||
permalink: '/docs/',
|
permalink: '/docs/',
|
||||||
source: path.join('@site', routeBasePath, source),
|
|
||||||
title: 'Bar',
|
title: 'Bar',
|
||||||
description: 'This is custom description',
|
description: 'This is custom description',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('docs with editUrl', async () => {
|
test('docs with editUrl', async () => {
|
||||||
const editUrl =
|
const {testMeta: testMetaLocal} = createTestHelpers({
|
||||||
'https://github.com/facebook/docusaurus/edit/master/website';
|
siteDir,
|
||||||
const source = path.join('foo', 'baz.md');
|
options: {
|
||||||
const options = {
|
routeBasePath,
|
||||||
routeBasePath,
|
editUrl: 'https://github.com/facebook/docusaurus/edit/master/website',
|
||||||
editUrl,
|
},
|
||||||
};
|
|
||||||
|
|
||||||
const data = await processMetadata({
|
|
||||||
source,
|
|
||||||
refDir: docsDir,
|
|
||||||
context,
|
context,
|
||||||
options,
|
|
||||||
env,
|
env,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(data).toEqual({
|
await testMetaLocal(docsDir, path.join('foo', 'baz.md'), {
|
||||||
id: 'foo/baz',
|
id: 'foo/baz',
|
||||||
unversionedId: 'foo/baz',
|
unversionedId: 'foo/baz',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/foo/bazSlug.html',
|
permalink: '/docs/foo/bazSlug.html',
|
||||||
source: path.join('@site', routeBasePath, source),
|
|
||||||
title: 'baz',
|
title: 'baz',
|
||||||
editUrl:
|
editUrl:
|
||||||
'https://github.com/facebook/docusaurus/edit/master/website/docs/foo/baz.md',
|
'https://github.com/facebook/docusaurus/edit/master/website/docs/foo/baz.md',
|
||||||
|
@ -147,57 +161,34 @@ describe('simple site', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('docs with custom editUrl & unrelated frontmatter', async () => {
|
test('docs with custom editUrl & unrelated frontmatter', async () => {
|
||||||
const source = 'lorem.md';
|
await testMeta(docsDir, 'lorem.md', {
|
||||||
const options = {
|
|
||||||
routeBasePath,
|
|
||||||
};
|
|
||||||
|
|
||||||
const data = await processMetadata({
|
|
||||||
source,
|
|
||||||
refDir: docsDir,
|
|
||||||
context,
|
|
||||||
options,
|
|
||||||
env,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(data).toEqual({
|
|
||||||
id: 'lorem',
|
id: 'lorem',
|
||||||
unversionedId: 'lorem',
|
unversionedId: 'lorem',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/lorem',
|
permalink: '/docs/lorem',
|
||||||
source: path.join('@site', routeBasePath, source),
|
|
||||||
title: 'lorem',
|
title: 'lorem',
|
||||||
editUrl: 'https://github.com/customUrl/docs/lorem.md',
|
editUrl: 'https://github.com/customUrl/docs/lorem.md',
|
||||||
description: 'Lorem ipsum.',
|
description: 'Lorem ipsum.',
|
||||||
});
|
});
|
||||||
|
|
||||||
// unrelated frontmatter is not part of metadata
|
|
||||||
// @ts-expect-error: It doesn't exist, so the test will show it's undefined.
|
|
||||||
expect(data.unrelated_frontmatter).toBeUndefined();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('docs with last update time and author', async () => {
|
test('docs with last update time and author', async () => {
|
||||||
const source = 'lorem.md';
|
const {testMeta: testMetaLocal} = createTestHelpers({
|
||||||
const options = {
|
siteDir,
|
||||||
routeBasePath,
|
options: {
|
||||||
showLastUpdateAuthor: true,
|
routeBasePath,
|
||||||
showLastUpdateTime: true,
|
showLastUpdateAuthor: true,
|
||||||
};
|
showLastUpdateTime: true,
|
||||||
|
},
|
||||||
const data = await processMetadata({
|
|
||||||
source,
|
|
||||||
refDir: docsDir,
|
|
||||||
context,
|
context,
|
||||||
options,
|
|
||||||
env,
|
env,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(data).toEqual({
|
await testMetaLocal(docsDir, 'lorem.md', {
|
||||||
id: 'lorem',
|
id: 'lorem',
|
||||||
unversionedId: 'lorem',
|
unversionedId: 'lorem',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/lorem',
|
permalink: '/docs/lorem',
|
||||||
source: path.join('@site', routeBasePath, source),
|
|
||||||
title: 'lorem',
|
title: 'lorem',
|
||||||
editUrl: 'https://github.com/customUrl/docs/lorem.md',
|
editUrl: 'https://github.com/customUrl/docs/lorem.md',
|
||||||
description: 'Lorem ipsum.',
|
description: 'Lorem ipsum.',
|
||||||
|
@ -207,27 +198,22 @@ describe('simple site', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('docs with null custom_edit_url', async () => {
|
test('docs with null custom_edit_url', async () => {
|
||||||
const source = 'ipsum.md';
|
const {testMeta: testMetaLocal} = createTestHelpers({
|
||||||
const options = {
|
siteDir,
|
||||||
routeBasePath,
|
options: {
|
||||||
showLastUpdateAuthor: true,
|
routeBasePath,
|
||||||
showLastUpdateTime: true,
|
showLastUpdateAuthor: true,
|
||||||
};
|
showLastUpdateTime: true,
|
||||||
|
},
|
||||||
const data = await processMetadata({
|
|
||||||
source,
|
|
||||||
refDir: docsDir,
|
|
||||||
context,
|
context,
|
||||||
options,
|
|
||||||
env,
|
env,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(data).toEqual({
|
await testMetaLocal(docsDir, 'ipsum.md', {
|
||||||
id: 'ipsum',
|
id: 'ipsum',
|
||||||
unversionedId: 'ipsum',
|
unversionedId: 'ipsum',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/ipsum',
|
permalink: '/docs/ipsum',
|
||||||
source: path.join('@site', routeBasePath, source),
|
|
||||||
title: 'ipsum',
|
title: 'ipsum',
|
||||||
editUrl: null,
|
editUrl: null,
|
||||||
description: 'Lorem ipsum.',
|
description: 'Lorem ipsum.',
|
||||||
|
@ -236,18 +222,61 @@ describe('simple site', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('docs with slugs', async () => {
|
||||||
|
await testSlug(
|
||||||
|
docsDir,
|
||||||
|
path.join('rootRelativeSlug.md'),
|
||||||
|
'/docs/rootRelativeSlug',
|
||||||
|
);
|
||||||
|
await testSlug(
|
||||||
|
docsDir,
|
||||||
|
path.join('rootAbsoluteSlug.md'),
|
||||||
|
'/docs/rootAbsoluteSlug',
|
||||||
|
);
|
||||||
|
await testSlug(
|
||||||
|
docsDir,
|
||||||
|
path.join('rootResolvedSlug.md'),
|
||||||
|
'/docs/hey/rootResolvedSlug',
|
||||||
|
);
|
||||||
|
await testSlug(
|
||||||
|
docsDir,
|
||||||
|
path.join('rootTryToEscapeSlug.md'),
|
||||||
|
'/docs/rootTryToEscapeSlug',
|
||||||
|
);
|
||||||
|
|
||||||
|
await testSlug(
|
||||||
|
docsDir,
|
||||||
|
path.join('slugs', 'absoluteSlug.md'),
|
||||||
|
'/docs/absoluteSlug',
|
||||||
|
);
|
||||||
|
await testSlug(
|
||||||
|
docsDir,
|
||||||
|
path.join('slugs', 'relativeSlug.md'),
|
||||||
|
'/docs/slugs/relativeSlug',
|
||||||
|
);
|
||||||
|
await testSlug(
|
||||||
|
docsDir,
|
||||||
|
path.join('slugs', 'resolvedSlug.md'),
|
||||||
|
'/docs/slugs/hey/resolvedSlug',
|
||||||
|
);
|
||||||
|
await testSlug(
|
||||||
|
docsDir,
|
||||||
|
path.join('slugs', 'tryToEscapeSlug.md'),
|
||||||
|
'/docs/tryToEscapeSlug',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('docs with invalid id', async () => {
|
test('docs with invalid id', async () => {
|
||||||
const badSiteDir = path.join(fixtureDir, 'bad-id-site');
|
const badSiteDir = path.join(fixtureDir, 'bad-id-site');
|
||||||
const options = {
|
|
||||||
routeBasePath,
|
|
||||||
};
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
processMetadata({
|
processMetadata({
|
||||||
source: 'invalid-id.md',
|
source: 'invalid-id.md',
|
||||||
refDir: path.join(badSiteDir, 'docs'),
|
refDir: path.join(badSiteDir, 'docs'),
|
||||||
context,
|
context,
|
||||||
options,
|
options: {
|
||||||
|
routeBasePath,
|
||||||
|
},
|
||||||
env,
|
env,
|
||||||
}),
|
}),
|
||||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
|
@ -255,38 +284,18 @@ describe('simple site', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('docs with invalid slug', async () => {
|
|
||||||
const badSiteDir = path.join(fixtureDir, 'bad-slug-site');
|
|
||||||
const options = {
|
|
||||||
routeBasePath,
|
|
||||||
};
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
processMetadata({
|
|
||||||
source: 'invalid-slug.md',
|
|
||||||
refDir: path.join(badSiteDir, 'docs'),
|
|
||||||
context,
|
|
||||||
options,
|
|
||||||
env,
|
|
||||||
}),
|
|
||||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
||||||
`"Document slug cannot include \\"/\\"."`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('docs with slug on doc home', async () => {
|
test('docs with slug on doc home', async () => {
|
||||||
const badSiteDir = path.join(fixtureDir, 'bad-slug-on-doc-home-site');
|
const badSiteDir = path.join(fixtureDir, 'bad-slug-on-doc-home-site');
|
||||||
const options = {
|
|
||||||
routeBasePath,
|
|
||||||
homePageId: 'docWithSlug',
|
|
||||||
};
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
processMetadata({
|
processMetadata({
|
||||||
source: 'docWithSlug.md',
|
source: 'docWithSlug.md',
|
||||||
refDir: path.join(badSiteDir, 'docs'),
|
refDir: path.join(badSiteDir, 'docs'),
|
||||||
context,
|
context,
|
||||||
options,
|
options: {
|
||||||
|
routeBasePath,
|
||||||
|
homePageId: 'docWithSlug',
|
||||||
|
},
|
||||||
env,
|
env,
|
||||||
}),
|
}),
|
||||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
|
@ -302,47 +311,30 @@ describe('versioned site', () => {
|
||||||
const docsDir = path.resolve(siteDir, routeBasePath);
|
const docsDir = path.resolve(siteDir, routeBasePath);
|
||||||
const env = loadEnv(siteDir);
|
const env = loadEnv(siteDir);
|
||||||
const {docsDir: versionedDir} = env.versioning;
|
const {docsDir: versionedDir} = env.versioning;
|
||||||
|
const options = {routeBasePath};
|
||||||
|
|
||||||
test('master/next docs', async () => {
|
const {testMeta, testSlug} = createTestHelpers({
|
||||||
const sourceA = path.join('foo', 'bar.md');
|
siteDir,
|
||||||
const sourceB = path.join('hello.md');
|
context,
|
||||||
const options = {
|
options,
|
||||||
routeBasePath,
|
env,
|
||||||
};
|
});
|
||||||
|
|
||||||
const [dataA, dataB] = await Promise.all([
|
test('next docs', async () => {
|
||||||
processMetadata({
|
await testMeta(docsDir, path.join('foo', 'bar.md'), {
|
||||||
source: sourceA,
|
|
||||||
refDir: docsDir,
|
|
||||||
context,
|
|
||||||
options,
|
|
||||||
env,
|
|
||||||
}),
|
|
||||||
processMetadata({
|
|
||||||
source: sourceB,
|
|
||||||
refDir: docsDir,
|
|
||||||
context,
|
|
||||||
options,
|
|
||||||
env,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(dataA).toEqual({
|
|
||||||
id: 'foo/bar',
|
id: 'foo/bar',
|
||||||
unversionedId: 'foo/bar',
|
unversionedId: 'foo/bar',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/next/foo/barSlug',
|
permalink: '/docs/next/foo/barSlug',
|
||||||
source: path.join('@site', routeBasePath, sourceA),
|
|
||||||
title: 'bar',
|
title: 'bar',
|
||||||
description: 'This is next version of bar.',
|
description: 'This is next version of bar.',
|
||||||
version: 'next',
|
version: 'next',
|
||||||
});
|
});
|
||||||
expect(dataB).toEqual({
|
await testMeta(docsDir, path.join('hello.md'), {
|
||||||
id: 'hello',
|
id: 'hello',
|
||||||
unversionedId: 'hello',
|
unversionedId: 'hello',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/next/hello',
|
permalink: '/docs/next/hello',
|
||||||
source: path.join('@site', routeBasePath, sourceB),
|
|
||||||
title: 'hello',
|
title: 'hello',
|
||||||
description: 'Hello next !',
|
description: 'Hello next !',
|
||||||
version: 'next',
|
version: 'next',
|
||||||
|
@ -350,84 +342,108 @@ describe('versioned site', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('versioned docs', async () => {
|
test('versioned docs', async () => {
|
||||||
const sourceA = path.join('version-1.0.0', 'foo', 'bar.md');
|
await testMeta(versionedDir, path.join('version-1.0.0', 'foo', 'bar.md'), {
|
||||||
const sourceB = path.join('version-1.0.0', 'hello.md');
|
|
||||||
const sourceC = path.join('version-1.0.1', 'foo', 'bar.md');
|
|
||||||
const sourceD = path.join('version-1.0.1', 'hello.md');
|
|
||||||
const options = {
|
|
||||||
routeBasePath,
|
|
||||||
};
|
|
||||||
|
|
||||||
const [dataA, dataB, dataC, dataD] = await Promise.all([
|
|
||||||
processMetadata({
|
|
||||||
source: sourceA,
|
|
||||||
refDir: versionedDir,
|
|
||||||
context,
|
|
||||||
options,
|
|
||||||
env,
|
|
||||||
}),
|
|
||||||
processMetadata({
|
|
||||||
source: sourceB,
|
|
||||||
refDir: versionedDir,
|
|
||||||
context,
|
|
||||||
options,
|
|
||||||
env,
|
|
||||||
}),
|
|
||||||
processMetadata({
|
|
||||||
source: sourceC,
|
|
||||||
refDir: versionedDir,
|
|
||||||
context,
|
|
||||||
options,
|
|
||||||
env,
|
|
||||||
}),
|
|
||||||
processMetadata({
|
|
||||||
source: sourceD,
|
|
||||||
refDir: versionedDir,
|
|
||||||
context,
|
|
||||||
options,
|
|
||||||
env,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(dataA).toEqual({
|
|
||||||
id: 'version-1.0.0/foo/bar',
|
id: 'version-1.0.0/foo/bar',
|
||||||
unversionedId: 'foo/bar',
|
unversionedId: 'foo/bar',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/1.0.0/foo/barSlug',
|
permalink: '/docs/1.0.0/foo/barSlug',
|
||||||
source: path.join('@site', path.relative(siteDir, versionedDir), sourceA),
|
|
||||||
title: 'bar',
|
title: 'bar',
|
||||||
description: 'Bar 1.0.0 !',
|
description: 'Bar 1.0.0 !',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
});
|
});
|
||||||
expect(dataB).toEqual({
|
await testMeta(versionedDir, path.join('version-1.0.0', 'hello.md'), {
|
||||||
id: 'version-1.0.0/hello',
|
id: 'version-1.0.0/hello',
|
||||||
unversionedId: 'hello',
|
unversionedId: 'hello',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/1.0.0/hello',
|
permalink: '/docs/1.0.0/hello',
|
||||||
source: path.join('@site', path.relative(siteDir, versionedDir), sourceB),
|
|
||||||
title: 'hello',
|
title: 'hello',
|
||||||
description: 'Hello 1.0.0 !',
|
description: 'Hello 1.0.0 !',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
});
|
});
|
||||||
expect(dataC).toEqual({
|
await testMeta(versionedDir, path.join('version-1.0.1', 'foo', 'bar.md'), {
|
||||||
id: 'version-1.0.1/foo/bar',
|
id: 'version-1.0.1/foo/bar',
|
||||||
unversionedId: 'foo/bar',
|
unversionedId: 'foo/bar',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/foo/bar',
|
permalink: '/docs/foo/bar',
|
||||||
source: path.join('@site', path.relative(siteDir, versionedDir), sourceC),
|
|
||||||
title: 'bar',
|
title: 'bar',
|
||||||
description: 'Bar 1.0.1 !',
|
description: 'Bar 1.0.1 !',
|
||||||
version: '1.0.1',
|
version: '1.0.1',
|
||||||
});
|
});
|
||||||
expect(dataD).toEqual({
|
await testMeta(versionedDir, path.join('version-1.0.1', 'hello.md'), {
|
||||||
id: 'version-1.0.1/hello',
|
id: 'version-1.0.1/hello',
|
||||||
unversionedId: 'hello',
|
unversionedId: 'hello',
|
||||||
isDocsHomePage: false,
|
isDocsHomePage: false,
|
||||||
permalink: '/docs/hello',
|
permalink: '/docs/hello',
|
||||||
source: path.join('@site', path.relative(siteDir, versionedDir), sourceD),
|
|
||||||
title: 'hello',
|
title: 'hello',
|
||||||
description: 'Hello 1.0.1 !',
|
description: 'Hello 1.0.1 !',
|
||||||
version: '1.0.1',
|
version: '1.0.1',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('next doc slugs', async () => {
|
||||||
|
await testSlug(
|
||||||
|
docsDir,
|
||||||
|
path.join('slugs', 'absoluteSlug.md'),
|
||||||
|
'/docs/next/absoluteSlug',
|
||||||
|
);
|
||||||
|
await testSlug(
|
||||||
|
docsDir,
|
||||||
|
path.join('slugs', 'relativeSlug.md'),
|
||||||
|
'/docs/next/slugs/relativeSlug',
|
||||||
|
);
|
||||||
|
await testSlug(
|
||||||
|
docsDir,
|
||||||
|
path.join('slugs', 'resolvedSlug.md'),
|
||||||
|
'/docs/next/slugs/hey/resolvedSlug',
|
||||||
|
);
|
||||||
|
await testSlug(
|
||||||
|
docsDir,
|
||||||
|
path.join('slugs', 'tryToEscapeSlug.md'),
|
||||||
|
'/docs/next/tryToEscapeSlug',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('versioned doc slugs', async () => {
|
||||||
|
await testSlug(
|
||||||
|
versionedDir,
|
||||||
|
path.join('version-withSlugs', 'rootAbsoluteSlug.md'),
|
||||||
|
'/docs/withSlugs/rootAbsoluteSlug',
|
||||||
|
);
|
||||||
|
await testSlug(
|
||||||
|
versionedDir,
|
||||||
|
path.join('version-withSlugs', 'rootRelativeSlug.md'),
|
||||||
|
'/docs/withSlugs/rootRelativeSlug',
|
||||||
|
);
|
||||||
|
await testSlug(
|
||||||
|
versionedDir,
|
||||||
|
path.join('version-withSlugs', 'rootResolvedSlug.md'),
|
||||||
|
'/docs/withSlugs/hey/rootResolvedSlug',
|
||||||
|
);
|
||||||
|
await testSlug(
|
||||||
|
versionedDir,
|
||||||
|
path.join('version-withSlugs', 'rootTryToEscapeSlug.md'),
|
||||||
|
'/docs/withSlugs/rootTryToEscapeSlug',
|
||||||
|
);
|
||||||
|
|
||||||
|
await testSlug(
|
||||||
|
versionedDir,
|
||||||
|
path.join('version-withSlugs', 'slugs', 'absoluteSlug.md'),
|
||||||
|
'/docs/withSlugs/absoluteSlug',
|
||||||
|
);
|
||||||
|
await testSlug(
|
||||||
|
versionedDir,
|
||||||
|
path.join('version-withSlugs', 'slugs', 'relativeSlug.md'),
|
||||||
|
'/docs/withSlugs/slugs/relativeSlug',
|
||||||
|
);
|
||||||
|
await testSlug(
|
||||||
|
versionedDir,
|
||||||
|
path.join('version-withSlugs', 'slugs', 'resolvedSlug.md'),
|
||||||
|
'/docs/withSlugs/slugs/hey/resolvedSlug',
|
||||||
|
);
|
||||||
|
await testSlug(
|
||||||
|
versionedDir,
|
||||||
|
path.join('version-withSlugs', 'slugs', 'tryToEscapeSlug.md'),
|
||||||
|
'/docs/withSlugs/tryToEscapeSlug',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import getSlug from '../slug';
|
||||||
|
|
||||||
|
describe('getSlug', () => {
|
||||||
|
test('should default to dirname/id', () => {
|
||||||
|
expect(getSlug({baseID: 'doc', dirName: '/dir'})).toEqual('/dir/doc');
|
||||||
|
expect(getSlug({baseID: 'doc', dirName: '/dir/subdir'})).toEqual(
|
||||||
|
'/dir/subdir/doc',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle current dir', () => {
|
||||||
|
expect(getSlug({baseID: 'doc', dirName: '.'})).toEqual('/doc');
|
||||||
|
expect(getSlug({baseID: 'doc', dirName: '/'})).toEqual('/doc');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should resolve absolute slug frontmatter', () => {
|
||||||
|
expect(
|
||||||
|
getSlug({baseID: 'any', dirName: '.', frontmatterSlug: '/abc/def'}),
|
||||||
|
).toEqual('/abc/def');
|
||||||
|
expect(
|
||||||
|
getSlug({baseID: 'any', dirName: './any', frontmatterSlug: '/abc/def'}),
|
||||||
|
).toEqual('/abc/def');
|
||||||
|
expect(
|
||||||
|
getSlug({
|
||||||
|
baseID: 'any',
|
||||||
|
dirName: './any/any',
|
||||||
|
frontmatterSlug: '/abc/def',
|
||||||
|
}),
|
||||||
|
).toEqual('/abc/def');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should resolve relative slug frontmatter', () => {
|
||||||
|
expect(
|
||||||
|
getSlug({baseID: 'any', dirName: '.', frontmatterSlug: 'abc/def'}),
|
||||||
|
).toEqual('/abc/def');
|
||||||
|
expect(
|
||||||
|
getSlug({baseID: 'any', dirName: '/dir', frontmatterSlug: 'abc/def'}),
|
||||||
|
).toEqual('/dir/abc/def');
|
||||||
|
expect(
|
||||||
|
getSlug({
|
||||||
|
baseID: 'any',
|
||||||
|
dirName: 'unslashedDir',
|
||||||
|
frontmatterSlug: 'abc/def',
|
||||||
|
}),
|
||||||
|
).toEqual('/unslashedDir/abc/def');
|
||||||
|
expect(
|
||||||
|
getSlug({
|
||||||
|
baseID: 'any',
|
||||||
|
dirName: 'dir/subdir',
|
||||||
|
frontmatterSlug: 'abc/def',
|
||||||
|
}),
|
||||||
|
).toEqual('/dir/subdir/abc/def');
|
||||||
|
expect(
|
||||||
|
getSlug({baseID: 'any', dirName: '/dir', frontmatterSlug: './abc/def'}),
|
||||||
|
).toEqual('/dir/abc/def');
|
||||||
|
expect(
|
||||||
|
getSlug({
|
||||||
|
baseID: 'any',
|
||||||
|
dirName: '/dir',
|
||||||
|
frontmatterSlug: './abc/../def',
|
||||||
|
}),
|
||||||
|
).toEqual('/dir/def');
|
||||||
|
expect(
|
||||||
|
getSlug({
|
||||||
|
baseID: 'any',
|
||||||
|
dirName: '/dir/subdir',
|
||||||
|
frontmatterSlug: '../abc/def',
|
||||||
|
}),
|
||||||
|
).toEqual('/dir/abc/def');
|
||||||
|
expect(
|
||||||
|
getSlug({
|
||||||
|
baseID: 'any',
|
||||||
|
dirName: '/dir/subdir',
|
||||||
|
frontmatterSlug: '../../../../../abc/../def',
|
||||||
|
}),
|
||||||
|
).toEqual('/def');
|
||||||
|
});
|
||||||
|
});
|
|
@ -187,7 +187,7 @@ describe('docsVersion', () => {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
expect(versionsPath).toEqual(getVersionsJSONFile(versionedSiteDir));
|
expect(versionsPath).toEqual(getVersionsJSONFile(versionedSiteDir));
|
||||||
expect(versions).toEqual(['2.0.0', '1.0.1', '1.0.0']);
|
expect(versions).toEqual(['2.0.0', '1.0.1', '1.0.0', 'withSlugs']);
|
||||||
expect(consoleMock).toHaveBeenCalledWith('Version 2.0.0 created!');
|
expect(consoleMock).toHaveBeenCalledWith('Version 2.0.0 created!');
|
||||||
|
|
||||||
copyMock.mockRestore();
|
copyMock.mockRestore();
|
||||||
|
|
|
@ -436,7 +436,10 @@ Available document ids=
|
||||||
// to be by version and pick only needed base metadata.
|
// to be by version and pick only needed base metadata.
|
||||||
if (versioning.enabled) {
|
if (versioning.enabled) {
|
||||||
const docsMetadataByVersion = groupBy(
|
const docsMetadataByVersion = groupBy(
|
||||||
Object.values(content.docsMetadata),
|
// sort to ensure consistent output for tests
|
||||||
|
Object.values(content.docsMetadata).sort((a, b) =>
|
||||||
|
a.id.localeCompare(b.id),
|
||||||
|
),
|
||||||
'version',
|
'version',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -22,9 +22,11 @@ import {
|
||||||
Env,
|
Env,
|
||||||
VersioningEnv,
|
VersioningEnv,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
import getSlug from './slug';
|
||||||
|
import {escapeRegExp} from 'lodash';
|
||||||
|
|
||||||
function removeVersionPrefix(str: string, version: string): string {
|
function removeVersionPrefix(str: string, version: string): string {
|
||||||
return str.replace(new RegExp(`^version-${version}/`), '');
|
return str.replace(new RegExp(`^version-${escapeRegExp(version)}/?`), '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function inferVersion(
|
function inferVersion(
|
||||||
|
@ -102,8 +104,12 @@ export default async function processMetadata({
|
||||||
const fileMarkdownPromise = parseMarkdownFile(filePath);
|
const fileMarkdownPromise = parseMarkdownFile(filePath);
|
||||||
const lastUpdatedPromise = lastUpdated(filePath, options);
|
const lastUpdatedPromise = lastUpdated(filePath, options);
|
||||||
|
|
||||||
const dirName = path.dirname(source);
|
const dirNameWithVersion = path.dirname(source); // ex: version-1.0.0/foo
|
||||||
const version = inferVersion(dirName, versioning);
|
const version = inferVersion(dirNameWithVersion, versioning); // ex: 1.0.0
|
||||||
|
const dirNameWithoutVersion = // ex: foo
|
||||||
|
version && version !== 'next'
|
||||||
|
? removeVersionPrefix(dirNameWithVersion, version)
|
||||||
|
: dirNameWithVersion;
|
||||||
|
|
||||||
// The version portion of the url path. Eg: 'next', '1.0.0', and ''.
|
// The version portion of the url path. Eg: 'next', '1.0.0', and ''.
|
||||||
const versionPath =
|
const versionPath =
|
||||||
|
@ -122,7 +128,9 @@ export default async function processMetadata({
|
||||||
if (baseID.includes('/')) {
|
if (baseID.includes('/')) {
|
||||||
throw new Error('Document id cannot include "/".');
|
throw new Error('Document id cannot include "/".');
|
||||||
}
|
}
|
||||||
const id = dirName !== '.' ? `${dirName}/${baseID}` : baseID;
|
|
||||||
|
const id =
|
||||||
|
dirNameWithVersion !== '.' ? `${dirNameWithVersion}/${baseID}` : baseID;
|
||||||
const unversionedId = version ? removeVersionPrefix(id, version) : id;
|
const unversionedId = version ? removeVersionPrefix(id, version) : id;
|
||||||
|
|
||||||
const isDocsHomePage = unversionedId === homePageId;
|
const isDocsHomePage = unversionedId === homePageId;
|
||||||
|
@ -132,34 +140,24 @@ export default async function processMetadata({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseSlug: string = frontMatter.slug || baseID;
|
const docSlug = isDocsHomePage
|
||||||
if (baseSlug.includes('/')) {
|
? '/'
|
||||||
throw new Error('Document slug cannot include "/".');
|
: getSlug({
|
||||||
}
|
baseID,
|
||||||
const slug = dirName !== '.' ? `${dirName}/${baseSlug}` : baseSlug;
|
dirName: dirNameWithoutVersion,
|
||||||
|
frontmatterSlug: frontMatter.slug,
|
||||||
|
});
|
||||||
|
|
||||||
// Default title is the id.
|
// Default title is the id.
|
||||||
const title: string = frontMatter.title || baseID;
|
const title: string = frontMatter.title || baseID;
|
||||||
|
|
||||||
const description: string = frontMatter.description || excerpt;
|
const description: string = frontMatter.description || excerpt;
|
||||||
|
|
||||||
// The last portion of the url path. Eg: 'foo/bar', 'bar'.
|
|
||||||
let routePath;
|
|
||||||
if (isDocsHomePage) {
|
|
||||||
// TODO can we remove this trailing / ?
|
|
||||||
// Seems it's not that easy...
|
|
||||||
// Related to https://github.com/facebook/docusaurus/issues/2917
|
|
||||||
routePath = '/';
|
|
||||||
} else {
|
|
||||||
routePath =
|
|
||||||
version && version !== 'next' ? removeVersionPrefix(slug, version) : slug;
|
|
||||||
}
|
|
||||||
|
|
||||||
const permalink = normalizeUrl([
|
const permalink = normalizeUrl([
|
||||||
baseUrl,
|
baseUrl,
|
||||||
routeBasePath,
|
routeBasePath,
|
||||||
versionPath,
|
versionPath,
|
||||||
routePath,
|
docSlug,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const {lastUpdatedAt, lastUpdatedBy} = await lastUpdatedPromise;
|
const {lastUpdatedAt, lastUpdatedBy} = await lastUpdatedPromise;
|
||||||
|
|
41
packages/docusaurus-plugin-content-docs/src/slug.ts
Normal file
41
packages/docusaurus-plugin-content-docs/src/slug.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
addLeadingSlash,
|
||||||
|
addTrailingSlash,
|
||||||
|
isValidPathname,
|
||||||
|
resolvePathname,
|
||||||
|
} from '@docusaurus/utils';
|
||||||
|
|
||||||
|
export default function getSlug({
|
||||||
|
baseID,
|
||||||
|
frontmatterSlug,
|
||||||
|
dirName,
|
||||||
|
}: {
|
||||||
|
baseID: string;
|
||||||
|
frontmatterSlug?: string;
|
||||||
|
dirName: string;
|
||||||
|
}) {
|
||||||
|
const baseSlug: string = frontmatterSlug || baseID;
|
||||||
|
let slug: string;
|
||||||
|
if (baseSlug.startsWith('/')) {
|
||||||
|
slug = baseSlug;
|
||||||
|
} else {
|
||||||
|
const resolveDirname =
|
||||||
|
dirName === '.' ? '/' : addLeadingSlash(addTrailingSlash(dirName));
|
||||||
|
slug = resolvePathname(baseSlug, resolveDirname);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValidPathname(slug)) {
|
||||||
|
throw new Error(
|
||||||
|
`Unable to resolve valid document slug. Maybe your slug frontmatter is incorrect? Doc id=${baseID} / dirName=${dirName} / frontmatterSlug=${frontmatterSlug} => bad result slug=${slug}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return slug;
|
||||||
|
}
|
|
@ -126,7 +126,7 @@ export interface MetadataRaw extends LastUpdateData {
|
||||||
source: string;
|
source: string;
|
||||||
permalink: string;
|
permalink: string;
|
||||||
sidebar_label?: string;
|
sidebar_label?: string;
|
||||||
editUrl?: string;
|
editUrl?: string | null;
|
||||||
version?: string;
|
version?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
"gray-matter": "^4.0.2",
|
"gray-matter": "^4.0.2",
|
||||||
"lodash.camelcase": "^4.3.0",
|
"lodash.camelcase": "^4.3.0",
|
||||||
"lodash.kebabcase": "^4.1.1"
|
"lodash.kebabcase": "^4.1.1",
|
||||||
|
"resolve-pathname": "^3.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.15.1"
|
"node": ">=10.15.1"
|
||||||
|
|
|
@ -25,6 +25,7 @@ import {
|
||||||
removeSuffix,
|
removeSuffix,
|
||||||
removePrefix,
|
removePrefix,
|
||||||
getFilePathForRoutePath,
|
getFilePathForRoutePath,
|
||||||
|
addLeadingSlash,
|
||||||
} from '../index';
|
} from '../index';
|
||||||
|
|
||||||
describe('load utils', () => {
|
describe('load utils', () => {
|
||||||
|
@ -412,6 +413,15 @@ describe('addTrailingSlash', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('addLeadingSlash', () => {
|
||||||
|
test('should no-op', () => {
|
||||||
|
expect(addLeadingSlash('/abc')).toEqual('/abc');
|
||||||
|
});
|
||||||
|
test('should add /', () => {
|
||||||
|
expect(addLeadingSlash('abc')).toEqual('/abc');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('removeTrailingSlash', () => {
|
describe('removeTrailingSlash', () => {
|
||||||
test('should no-op', () => {
|
test('should no-op', () => {
|
||||||
expect(removeTrailingSlash('/abcd')).toEqual('/abcd');
|
expect(removeTrailingSlash('/abcd')).toEqual('/abcd');
|
||||||
|
|
|
@ -14,6 +14,9 @@ import escapeStringRegexp from 'escape-string-regexp';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import {URL} from 'url';
|
import {URL} from 'url';
|
||||||
|
|
||||||
|
// @ts-expect-error: no typedefs :s
|
||||||
|
import resolvePathnameUnsafe from 'resolve-pathname';
|
||||||
|
|
||||||
const fileHash = new Map();
|
const fileHash = new Map();
|
||||||
export async function generate(
|
export async function generate(
|
||||||
generatedFilesDir: string,
|
generatedFilesDir: string,
|
||||||
|
@ -361,12 +364,20 @@ export function isValidPathname(str: string): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
// weird, but is there a better way?
|
||||||
return new URL(str, 'https://domain.com').pathname === str;
|
return new URL(str, 'https://domain.com').pathname === str;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolve pathname and fail fast if resolution fails
|
||||||
|
export function resolvePathname(to: string, from?: string) {
|
||||||
|
return resolvePathnameUnsafe(to, from);
|
||||||
|
}
|
||||||
|
export function addLeadingSlash(str: string): string {
|
||||||
|
return str.startsWith('/') ? str : `/${str}`;
|
||||||
|
}
|
||||||
export function addTrailingSlash(str: string): string {
|
export function addTrailingSlash(str: string): string {
|
||||||
return str.endsWith('/') ? str : `${str}/`;
|
return str.endsWith('/') ? str : `${str}/`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
id: resources
|
id: resources
|
||||||
title: Awesome Resources
|
title: Awesome Resources
|
||||||
|
slug: /resources
|
||||||
---
|
---
|
||||||
|
|
||||||
A curated list of interesting Docusaurus community projects.
|
A curated list of interesting Docusaurus community projects.
|
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
id: support
|
id: support
|
||||||
title: Support
|
title: Support
|
||||||
|
slug: /support
|
||||||
---
|
---
|
||||||
|
|
||||||
Docusaurus has a community of thousands of developers.
|
Docusaurus has a community of thousands of developers.
|
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
id: team
|
id: team
|
||||||
title: Team
|
title: Team
|
||||||
|
slug: /team
|
||||||
---
|
---
|
||||||
|
|
||||||
## Active Team
|
## Active Team
|
|
@ -52,9 +52,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
community: [
|
community: [
|
||||||
'support',
|
'community/support',
|
||||||
'team',
|
'community/team',
|
||||||
'resources',
|
'community/resources',
|
||||||
{
|
{
|
||||||
type: 'link',
|
type: 'link',
|
||||||
href: '/showcase',
|
href: '/showcase',
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue