mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 15:47:23 +02:00
* feat(v2): add option validation lifecycle method * chore(v2): add dependencies * chore(v2): add yup dependency * feat(v2): add option validation for plugin-content-docs * chore(v2): add facebook copyright * refactor(v2): remove unused variable * chore(v2): add dependencies * chore(v2): add copyright * fix(v2): use strict for option validation * feat(v2): add option validation for plugin-content-pages * feat(v2): add schema for plugin-google-analytics and plugin-google-gtag * feat(v2): add option validation for plugin-sitemap * chore(v2): add dependency for yup * fix(v2): remove strict to allow normalization * refactor(v2): refactor validate method * feat(v2): modify existing tests * feat(v2): add tests for plugin normalization * style(v2): use a more descriptive filename for schema * feat(v2): add normalization tests * feat(v2): add more tests for option validation * refactor(v2): remove unused code * refactor(v2): remove unused code * refactor(v2): refactor methods and types * feat(v2): replace Yup with Joi * fix(v2): fix plugin-content-docs schema * feat(v2): modify tests for plugin-content-docs * fix(v2): fix a typo * refactor(v2): improve tests and refactor code * feat(v2): support both commonjs and ES modules * refactor(v2): refactor validateOption method * style(v2): fix eslint errors and typo in types * chore(v2): remove unused yup dependency * style(v2): standardize naming across official plugins * chore(v2): update test snapshots * chore(v2): remove obsolete snapshots * chore(v2): fix a typo and check test * feat(v2): add validation for new field * feat(v2): add test for new field
442 lines
14 KiB
TypeScript
442 lines
14 KiB
TypeScript
/**
|
|
* 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 path from 'path';
|
|
import {validate} from 'webpack';
|
|
import {isMatch} from 'picomatch';
|
|
import commander from 'commander';
|
|
import fs from 'fs-extra';
|
|
import pluginContentDocs from '../index';
|
|
import loadEnv from '../env';
|
|
import normalizePluginOptions from './pluginOptionSchema.test';
|
|
import {loadContext} from '@docusaurus/core/src/server/index';
|
|
import {applyConfigureWebpack} from '@docusaurus/core/src/webpack/utils';
|
|
import {RouteConfig} from '@docusaurus/types';
|
|
import {posixPath} from '@docusaurus/utils';
|
|
import {sortConfig} from '@docusaurus/core/src/server/plugins';
|
|
|
|
import * as version from '../version';
|
|
|
|
const createFakeActions = (
|
|
routeConfigs: RouteConfig[],
|
|
contentDir,
|
|
dataContainer?,
|
|
) => {
|
|
return {
|
|
addRoute: (config: RouteConfig) => {
|
|
routeConfigs.push(config);
|
|
},
|
|
createData: async (name, content) => {
|
|
if (dataContainer) {
|
|
dataContainer[name] = content;
|
|
}
|
|
return path.join(contentDir, name);
|
|
},
|
|
};
|
|
};
|
|
|
|
test('site with wrong sidebar file', async () => {
|
|
const siteDir = path.join(__dirname, '__fixtures__', 'simple-site');
|
|
const context = loadContext(siteDir);
|
|
const sidebarPath = path.join(siteDir, 'wrong-sidebars.json');
|
|
const plugin = pluginContentDocs(
|
|
context,
|
|
normalizePluginOptions({
|
|
sidebarPath,
|
|
}),
|
|
);
|
|
await expect(plugin.loadContent()).rejects.toThrowErrorMatchingSnapshot();
|
|
});
|
|
|
|
describe('empty/no docs website', () => {
|
|
const siteDir = path.join(__dirname, '__fixtures__', 'empty-site');
|
|
const context = loadContext(siteDir);
|
|
|
|
test('no files in docs folder', async () => {
|
|
await fs.ensureDir(path.join(siteDir, 'docs'));
|
|
const plugin = pluginContentDocs(context, normalizePluginOptions({}));
|
|
const content = await plugin.loadContent();
|
|
const {docsMetadata, docsSidebars} = content;
|
|
expect(docsMetadata).toMatchInlineSnapshot(`Object {}`);
|
|
expect(docsSidebars).toMatchInlineSnapshot(`Object {}`);
|
|
|
|
const routeConfigs = [];
|
|
const pluginContentDir = path.join(context.generatedFilesDir, plugin.name);
|
|
const actions = createFakeActions(routeConfigs, pluginContentDir);
|
|
|
|
await plugin.contentLoaded({
|
|
content,
|
|
actions,
|
|
});
|
|
|
|
expect(routeConfigs).toEqual([]);
|
|
});
|
|
|
|
test('docs folder does not exist', async () => {
|
|
const plugin = pluginContentDocs(
|
|
context,
|
|
normalizePluginOptions({
|
|
path: '/path/does/not/exist/',
|
|
}),
|
|
);
|
|
const content = await plugin.loadContent();
|
|
expect(content).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('simple website', () => {
|
|
const siteDir = path.join(__dirname, '__fixtures__', 'simple-site');
|
|
const context = loadContext(siteDir);
|
|
const sidebarPath = path.join(siteDir, 'sidebars.json');
|
|
const pluginPath = 'docs';
|
|
const plugin = pluginContentDocs(
|
|
context,
|
|
normalizePluginOptions({
|
|
path: pluginPath,
|
|
sidebarPath,
|
|
homePageId: 'hello',
|
|
}),
|
|
);
|
|
const pluginContentDir = path.join(context.generatedFilesDir, plugin.name);
|
|
|
|
test('extendCli - docsVersion', () => {
|
|
const mock = jest.spyOn(version, 'docsVersion').mockImplementation();
|
|
const cli = new commander.Command();
|
|
plugin.extendCli(cli);
|
|
cli.parse(['node', 'test', 'docs:version', '1.0.0']);
|
|
expect(mock).toHaveBeenCalledWith('1.0.0', siteDir, {
|
|
path: pluginPath,
|
|
sidebarPath,
|
|
});
|
|
mock.mockRestore();
|
|
});
|
|
|
|
test('getPathToWatch', () => {
|
|
const pathToWatch = plugin.getPathsToWatch();
|
|
const matchPattern = pathToWatch.map((filepath) =>
|
|
posixPath(path.relative(siteDir, filepath)),
|
|
);
|
|
expect(matchPattern).not.toEqual([]);
|
|
expect(matchPattern).toMatchInlineSnapshot(`
|
|
Array [
|
|
"docs/**/*.{md,mdx}",
|
|
"sidebars.json",
|
|
]
|
|
`);
|
|
expect(isMatch('docs/hello.md', matchPattern)).toEqual(true);
|
|
expect(isMatch('docs/hello.mdx', matchPattern)).toEqual(true);
|
|
expect(isMatch('docs/foo/bar.md', matchPattern)).toEqual(true);
|
|
expect(isMatch('docs/hello.js', matchPattern)).toEqual(false);
|
|
expect(isMatch('docs/super.mdl', matchPattern)).toEqual(false);
|
|
expect(isMatch('docs/mdx', matchPattern)).toEqual(false);
|
|
expect(isMatch('sidebars.json', matchPattern)).toEqual(true);
|
|
expect(isMatch('versioned_docs/hello.md', matchPattern)).toEqual(false);
|
|
expect(isMatch('hello.md', matchPattern)).toEqual(false);
|
|
expect(isMatch('super/docs/hello.md', matchPattern)).toEqual(false);
|
|
});
|
|
|
|
test('configureWebpack', async () => {
|
|
const config = applyConfigureWebpack(
|
|
plugin.configureWebpack,
|
|
{
|
|
entry: './src/index.js',
|
|
output: {
|
|
filename: 'main.js',
|
|
path: path.resolve(__dirname, 'dist'),
|
|
},
|
|
},
|
|
false,
|
|
);
|
|
const errors = validate(config);
|
|
expect(errors.length).toBe(0);
|
|
});
|
|
|
|
test('content', async () => {
|
|
const content = await plugin.loadContent();
|
|
const {
|
|
docsMetadata,
|
|
docsSidebars,
|
|
versionToSidebars,
|
|
permalinkToSidebar,
|
|
} = content;
|
|
expect(versionToSidebars).toEqual({});
|
|
expect(docsMetadata.hello).toEqual({
|
|
id: 'hello',
|
|
isDocsHomePage: true,
|
|
permalink: '/docs/',
|
|
previous: {
|
|
title: 'baz',
|
|
permalink: '/docs/foo/bazSlug.html',
|
|
},
|
|
sidebar: 'docs',
|
|
source: path.join('@site', pluginPath, 'hello.md'),
|
|
title: 'Hello, World !',
|
|
description: 'Hi, Endilie here :)',
|
|
latestVersionMainDocPermalink: undefined,
|
|
});
|
|
|
|
expect(docsMetadata['foo/bar']).toEqual({
|
|
id: 'foo/bar',
|
|
isDocsHomePage: false,
|
|
next: {
|
|
title: 'baz',
|
|
permalink: '/docs/foo/bazSlug.html',
|
|
},
|
|
permalink: '/docs/foo/bar',
|
|
sidebar: 'docs',
|
|
source: path.join('@site', pluginPath, 'foo', 'bar.md'),
|
|
title: 'Bar',
|
|
description: 'This is custom description',
|
|
latestVersionMainDocPermalink: undefined,
|
|
});
|
|
|
|
expect(docsSidebars).toMatchSnapshot();
|
|
|
|
const routeConfigs = [];
|
|
const dataContainer = {};
|
|
const actions = createFakeActions(
|
|
routeConfigs,
|
|
pluginContentDir,
|
|
dataContainer,
|
|
);
|
|
|
|
await plugin.contentLoaded({
|
|
content,
|
|
actions,
|
|
});
|
|
|
|
// There is only one nested docs route for simple site
|
|
const baseMetadata = JSON.parse(dataContainer['docs-route-ff2.json']);
|
|
expect(baseMetadata.docsSidebars).toEqual(docsSidebars);
|
|
expect(baseMetadata.permalinkToSidebar).toEqual(permalinkToSidebar);
|
|
|
|
// Sort the route config like in src/server/plugins/index.ts for consistent snapshot ordering
|
|
sortConfig(routeConfigs);
|
|
|
|
expect(routeConfigs).not.toEqual([]);
|
|
expect(routeConfigs).toMatchSnapshot();
|
|
});
|
|
});
|
|
|
|
describe('versioned website', () => {
|
|
const siteDir = path.join(__dirname, '__fixtures__', 'versioned-site');
|
|
const context = loadContext(siteDir);
|
|
const sidebarPath = path.join(siteDir, 'sidebars.json');
|
|
const routeBasePath = 'docs';
|
|
const plugin = pluginContentDocs(
|
|
context,
|
|
normalizePluginOptions({
|
|
routeBasePath,
|
|
sidebarPath,
|
|
homePageId: 'hello',
|
|
}),
|
|
);
|
|
const env = loadEnv(siteDir);
|
|
const {docsDir: versionedDir} = env.versioning;
|
|
const pluginContentDir = path.join(context.generatedFilesDir, plugin.name);
|
|
|
|
test('extendCli - docsVersion', () => {
|
|
const mock = jest.spyOn(version, 'docsVersion').mockImplementation();
|
|
const cli = new commander.Command();
|
|
plugin.extendCli(cli);
|
|
cli.parse(['node', 'test', 'docs:version', '2.0.0']);
|
|
expect(mock).toHaveBeenCalledWith('2.0.0', siteDir, {
|
|
path: routeBasePath,
|
|
sidebarPath,
|
|
});
|
|
mock.mockRestore();
|
|
});
|
|
|
|
test('getPathToWatch', () => {
|
|
const pathToWatch = plugin.getPathsToWatch();
|
|
const matchPattern = pathToWatch.map((filepath) =>
|
|
posixPath(path.relative(siteDir, filepath)),
|
|
);
|
|
expect(matchPattern).not.toEqual([]);
|
|
expect(matchPattern).toMatchInlineSnapshot(`
|
|
Array [
|
|
"docs/**/*.{md,mdx}",
|
|
"versioned_sidebars/version-1.0.1-sidebars.json",
|
|
"versioned_sidebars/version-1.0.0-sidebars.json",
|
|
"versioned_docs/version-1.0.1/**/*.{md,mdx}",
|
|
"versioned_docs/version-1.0.0/**/*.{md,mdx}",
|
|
"sidebars.json",
|
|
]
|
|
`);
|
|
expect(isMatch('docs/hello.md', matchPattern)).toEqual(true);
|
|
expect(isMatch('docs/hello.mdx', matchPattern)).toEqual(true);
|
|
expect(isMatch('docs/foo/bar.md', matchPattern)).toEqual(true);
|
|
expect(isMatch('sidebars.json', matchPattern)).toEqual(true);
|
|
expect(
|
|
isMatch('versioned_docs/version-1.0.0/hello.md', matchPattern),
|
|
).toEqual(true);
|
|
expect(
|
|
isMatch('versioned_docs/version-1.0.0/foo/bar.md', matchPattern),
|
|
).toEqual(true);
|
|
expect(
|
|
isMatch('versioned_sidebars/version-1.0.0-sidebars.json', matchPattern),
|
|
).toEqual(true);
|
|
|
|
// Non existing version
|
|
expect(
|
|
isMatch('versioned_docs/version-2.0.0/foo/bar.md', matchPattern),
|
|
).toEqual(false);
|
|
expect(
|
|
isMatch('versioned_docs/version-2.0.0/hello.md', matchPattern),
|
|
).toEqual(false);
|
|
expect(
|
|
isMatch('versioned_sidebars/version-2.0.0-sidebars.json', matchPattern),
|
|
).toEqual(false);
|
|
|
|
expect(isMatch('docs/hello.js', matchPattern)).toEqual(false);
|
|
expect(isMatch('docs/super.mdl', matchPattern)).toEqual(false);
|
|
expect(isMatch('docs/mdx', matchPattern)).toEqual(false);
|
|
expect(isMatch('hello.md', matchPattern)).toEqual(false);
|
|
expect(isMatch('super/docs/hello.md', matchPattern)).toEqual(false);
|
|
});
|
|
|
|
test('content', async () => {
|
|
const content = await plugin.loadContent();
|
|
const {
|
|
docsMetadata,
|
|
docsSidebars,
|
|
versionToSidebars,
|
|
permalinkToSidebar,
|
|
} = content;
|
|
|
|
// foo/baz.md only exists in version -1.0.0
|
|
expect(docsMetadata['foo/baz']).toBeUndefined();
|
|
expect(docsMetadata['version-1.0.1/foo/baz']).toBeUndefined();
|
|
expect(docsMetadata['foo/bar']).toEqual({
|
|
id: 'foo/bar',
|
|
isDocsHomePage: false,
|
|
permalink: '/docs/next/foo/barSlug',
|
|
source: path.join('@site', routeBasePath, 'foo', 'bar.md'),
|
|
title: 'bar',
|
|
description: 'This is next version of bar.',
|
|
version: 'next',
|
|
sidebar: 'docs',
|
|
next: {
|
|
title: 'hello',
|
|
permalink: '/docs/next/',
|
|
},
|
|
});
|
|
expect(docsMetadata.hello).toEqual({
|
|
id: 'hello',
|
|
isDocsHomePage: true,
|
|
permalink: '/docs/next/',
|
|
source: path.join('@site', routeBasePath, 'hello.md'),
|
|
title: 'hello',
|
|
description: 'Hello next !',
|
|
version: 'next',
|
|
sidebar: 'docs',
|
|
previous: {
|
|
title: 'bar',
|
|
permalink: '/docs/next/foo/barSlug',
|
|
},
|
|
});
|
|
expect(docsMetadata['version-1.0.1/hello']).toEqual({
|
|
id: 'version-1.0.1/hello',
|
|
isDocsHomePage: true,
|
|
permalink: '/docs/',
|
|
source: path.join(
|
|
'@site',
|
|
path.relative(siteDir, versionedDir),
|
|
'version-1.0.1',
|
|
'hello.md',
|
|
),
|
|
title: 'hello',
|
|
description: 'Hello 1.0.1 !',
|
|
version: '1.0.1',
|
|
sidebar: 'version-1.0.1/docs',
|
|
previous: {
|
|
title: 'bar',
|
|
permalink: '/docs/foo/bar',
|
|
},
|
|
latestVersionMainDocPermalink: undefined,
|
|
});
|
|
expect(docsMetadata['version-1.0.0/foo/baz']).toEqual({
|
|
id: 'version-1.0.0/foo/baz',
|
|
isDocsHomePage: false,
|
|
permalink: '/docs/1.0.0/foo/baz',
|
|
source: path.join(
|
|
'@site',
|
|
path.relative(siteDir, versionedDir),
|
|
'version-1.0.0',
|
|
'foo',
|
|
'baz.md',
|
|
),
|
|
title: 'baz',
|
|
description:
|
|
'Baz 1.0.0 ! This will be deleted in next subsequent versions.',
|
|
version: '1.0.0',
|
|
sidebar: 'version-1.0.0/docs',
|
|
next: {
|
|
title: 'hello',
|
|
permalink: '/docs/1.0.0/',
|
|
},
|
|
previous: {
|
|
title: 'bar',
|
|
permalink: '/docs/1.0.0/foo/barSlug',
|
|
},
|
|
});
|
|
|
|
expect(docsSidebars).toMatchSnapshot('all sidebars');
|
|
expect(versionToSidebars).toMatchSnapshot(
|
|
'sidebars needed for each version',
|
|
);
|
|
const routeConfigs = [];
|
|
const dataContainer = {};
|
|
const actions = createFakeActions(
|
|
routeConfigs,
|
|
pluginContentDir,
|
|
dataContainer,
|
|
);
|
|
await plugin.contentLoaded({
|
|
content,
|
|
actions,
|
|
});
|
|
|
|
// The created base metadata for each nested docs route is smartly chunked/ splitted across version
|
|
const latestVersionBaseMetadata = JSON.parse(
|
|
dataContainer['docs-route-ff2.json'],
|
|
);
|
|
expect(latestVersionBaseMetadata).toMatchSnapshot(
|
|
'base metadata for latest version',
|
|
);
|
|
expect(latestVersionBaseMetadata.docsSidebars).not.toEqual(docsSidebars);
|
|
expect(latestVersionBaseMetadata.permalinkToSidebar).not.toEqual(
|
|
permalinkToSidebar,
|
|
);
|
|
const nextVersionBaseMetadata = JSON.parse(
|
|
dataContainer['docs-next-route-1c8.json'],
|
|
);
|
|
expect(nextVersionBaseMetadata).toMatchSnapshot(
|
|
'base metadata for next version',
|
|
);
|
|
expect(nextVersionBaseMetadata.docsSidebars).not.toEqual(docsSidebars);
|
|
expect(nextVersionBaseMetadata.permalinkToSidebar).not.toEqual(
|
|
permalinkToSidebar,
|
|
);
|
|
const firstVersionBaseMetadata = JSON.parse(
|
|
dataContainer['docs-1-0-0-route-660.json'],
|
|
);
|
|
expect(firstVersionBaseMetadata).toMatchSnapshot(
|
|
'base metadata for first version',
|
|
);
|
|
expect(nextVersionBaseMetadata.docsSidebars).not.toEqual(docsSidebars);
|
|
expect(nextVersionBaseMetadata.permalinkToSidebar).not.toEqual(
|
|
permalinkToSidebar,
|
|
);
|
|
|
|
// Sort the route config like in src/server/plugins/index.ts for consistent snapshot ordering
|
|
sortConfig(routeConfigs);
|
|
|
|
expect(routeConfigs).not.toEqual([]);
|
|
expect(routeConfigs).toMatchSnapshot();
|
|
});
|
|
});
|