mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-30 18:58:36 +02:00
test: improve test coverage; properly test core client APIs (#6905)
* test: improve test coverage * fix
This commit is contained in:
parent
76cb012209
commit
d85cee576d
41 changed files with 1400 additions and 753 deletions
|
@ -36,18 +36,18 @@ export default {
|
||||||
// Jest can't resolve CSS or asset imports
|
// Jest can't resolve CSS or asset imports
|
||||||
'^.+\\.(css|jpe?g|png|svg)$': '<rootDir>/jest/emptyModule.js',
|
'^.+\\.(css|jpe?g|png|svg)$': '<rootDir>/jest/emptyModule.js',
|
||||||
|
|
||||||
// TODO we need to allow Jest to resolve core Webpack aliases automatically
|
// Using src instead of lib, so we always get fresh source
|
||||||
'@docusaurus/(browserContext|BrowserOnly|ComponentCreator|constants|docusaurusContext|ExecutionEnvironment|Head|Interpolate|isInternalUrl|Link|Noop|renderRoutes|router|Translate|use.*)':
|
'@docusaurus/(browserContext|BrowserOnly|ComponentCreator|constants|docusaurusContext|ExecutionEnvironment|Head|Interpolate|isInternalUrl|Link|Noop|renderRoutes|router|Translate|use.*)':
|
||||||
'@docusaurus/core/lib/client/exports/$1',
|
'@docusaurus/core/src/client/exports/$1',
|
||||||
// Maybe point to a fixture?
|
// Maybe point to a fixture?
|
||||||
'@generated/.*': '<rootDir>/jest/emptyModule.js',
|
'@generated/.*': '<rootDir>/jest/emptyModule.js',
|
||||||
// TODO use "projects" + multiple configs if we work on another theme?
|
// TODO use "projects" + multiple configs if we work on another theme?
|
||||||
'@theme/(.*)': '@docusaurus/theme-classic/src/theme/$1',
|
'@theme/(.*)': '@docusaurus/theme-classic/src/theme/$1',
|
||||||
'@site/(.*)': 'website/$1',
|
'@site/(.*)': 'website/$1',
|
||||||
|
|
||||||
// TODO why Jest can't figure node package entry points?
|
// Using src instead of lib, so we always get fresh source
|
||||||
'@docusaurus/plugin-content-docs/client':
|
'@docusaurus/plugin-content-docs/client':
|
||||||
'@docusaurus/plugin-content-docs/lib/client/index.js',
|
'@docusaurus/plugin-content-docs/src/client/index.ts',
|
||||||
},
|
},
|
||||||
globals: {
|
globals: {
|
||||||
window: {
|
window: {
|
||||||
|
|
|
@ -13,3 +13,5 @@ Lorem ipsum
|
||||||
Some content here
|
Some content here
|
||||||
|
|
||||||
## I ♥ unicode.
|
## I ♥ unicode.
|
||||||
|
|
||||||
|
export const c = 1;
|
||||||
|
|
|
@ -114,6 +114,8 @@ Lorem ipsum
|
||||||
Some content here
|
Some content here
|
||||||
|
|
||||||
## I ♥ unicode.
|
## I ♥ unicode.
|
||||||
|
|
||||||
|
export const c = 1;
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -241,5 +243,7 @@ Lorem ipsum
|
||||||
Some content here
|
Some content here
|
||||||
|
|
||||||
## I ♥ unicode.
|
## I ♥ unicode.
|
||||||
|
|
||||||
|
export const c = 1;
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -31,7 +31,6 @@ export function toValue(node: PhrasingContent | Heading): string {
|
||||||
case 'link':
|
case 'link':
|
||||||
return stringifyContent(node);
|
return stringifyContent(node);
|
||||||
default:
|
default:
|
||||||
}
|
|
||||||
|
|
||||||
return toString(node);
|
return toString(node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`toGlobalDataVersion generates the right docs, sidebars, and metadata 1`] = `
|
||||||
|
Object {
|
||||||
|
"docs": Array [
|
||||||
|
Object {
|
||||||
|
"id": "main",
|
||||||
|
"path": "/current/main",
|
||||||
|
"sidebar": "tutorial",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "doc",
|
||||||
|
"path": "/current/doc",
|
||||||
|
"sidebar": "tutorial",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"id": "/current/generated",
|
||||||
|
"path": "/current/generated",
|
||||||
|
"sidebar": "tutorial",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"isLast": true,
|
||||||
|
"label": "Label",
|
||||||
|
"mainDocId": "main",
|
||||||
|
"name": "current",
|
||||||
|
"path": "/current",
|
||||||
|
"sidebars": Object {
|
||||||
|
"another": Object {
|
||||||
|
"link": Object {
|
||||||
|
"label": "Generated",
|
||||||
|
"path": "/current/generated",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"links": Object {},
|
||||||
|
"tutorial": Object {
|
||||||
|
"link": Object {
|
||||||
|
"label": "main",
|
||||||
|
"path": "/current/main",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
|
@ -0,0 +1,100 @@
|
||||||
|
/**
|
||||||
|
* 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 {toGlobalDataVersion} from '../globalData';
|
||||||
|
|
||||||
|
describe('toGlobalDataVersion', () => {
|
||||||
|
it('generates the right docs, sidebars, and metadata', () => {
|
||||||
|
expect(
|
||||||
|
toGlobalDataVersion({
|
||||||
|
versionName: 'current',
|
||||||
|
versionLabel: 'Label',
|
||||||
|
isLast: true,
|
||||||
|
versionPath: '/current',
|
||||||
|
mainDocId: 'main',
|
||||||
|
docs: [
|
||||||
|
{
|
||||||
|
unversionedId: 'main',
|
||||||
|
permalink: '/current/main',
|
||||||
|
sidebar: 'tutorial',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
unversionedId: 'doc',
|
||||||
|
permalink: '/current/doc',
|
||||||
|
sidebar: 'tutorial',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sidebars: {
|
||||||
|
another: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Generated',
|
||||||
|
link: {
|
||||||
|
type: 'generated-index',
|
||||||
|
permalink: '/current/generated',
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'doc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tutorial: [
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'main',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
label: 'Generated',
|
||||||
|
link: {
|
||||||
|
type: 'generated-index',
|
||||||
|
permalink: '/current/generated',
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
id: 'doc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
href: 'foo',
|
||||||
|
label: 'Foo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
href: 'bar',
|
||||||
|
label: 'Bar',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
categoryGeneratedIndices: [
|
||||||
|
{
|
||||||
|
title: 'Generated',
|
||||||
|
slug: '/current/generated',
|
||||||
|
permalink: '/current/generated',
|
||||||
|
sidebar: 'tutorial',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
versionBanner: 'unreleased',
|
||||||
|
versionBadge: true,
|
||||||
|
versionClassName: 'current-cls',
|
||||||
|
tagsPath: '/current/tags',
|
||||||
|
contentPath: '',
|
||||||
|
contentPathLocalized: '',
|
||||||
|
sidebarFilePath: '',
|
||||||
|
routePriority: 0.5,
|
||||||
|
}),
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -111,6 +111,27 @@ describe('getSlug', () => {
|
||||||
).toBe('/dir with spâce/hey $hello/my dôc');
|
).toBe('/dir with spâce/hey $hello/my dôc');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('throws for invalid routes', () => {
|
||||||
|
expect(() =>
|
||||||
|
getSlug({
|
||||||
|
baseID: 'my dôc',
|
||||||
|
source: '@site/docs/dir with spâce/hey $hello/doc.md',
|
||||||
|
sourceDirName: '/dir with spâce/hey $hello',
|
||||||
|
frontMatterSlug: '//',
|
||||||
|
}),
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"We couldn't compute a valid slug for document with ID \\"my dôc\\" in \\"/dir with spâce/hey $hello\\" directory.
|
||||||
|
The slug we computed looks invalid: //.
|
||||||
|
Maybe your slug front matter is incorrect or there are special characters in the file path?
|
||||||
|
By using front matter to set a custom slug, you should be able to fix this error:
|
||||||
|
|
||||||
|
---
|
||||||
|
slug: /my/customDocPath
|
||||||
|
---
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
it('handles current dir', () => {
|
it('handles current dir', () => {
|
||||||
expect(
|
expect(
|
||||||
getSlug({baseID: 'doc', source: '@site/docs/doc.md', sourceDirName: '.'}),
|
getSlug({baseID: 'doc', source: '@site/docs/doc.md', sourceDirName: '.'}),
|
||||||
|
|
|
@ -26,8 +26,8 @@ const DefaultI18N: I18n = {
|
||||||
localeConfigs: {},
|
localeConfigs: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('version paths', () => {
|
describe('getVersionsFilePath', () => {
|
||||||
it('getVersionsFilePath', () => {
|
it('works', () => {
|
||||||
expect(getVersionsFilePath('someSiteDir', DEFAULT_PLUGIN_ID)).toBe(
|
expect(getVersionsFilePath('someSiteDir', DEFAULT_PLUGIN_ID)).toBe(
|
||||||
`someSiteDir${path.sep}versions.json`,
|
`someSiteDir${path.sep}versions.json`,
|
||||||
);
|
);
|
||||||
|
@ -35,8 +35,10 @@ describe('version paths', () => {
|
||||||
`otherSite${path.sep}dir${path.sep}pluginId_versions.json`,
|
`otherSite${path.sep}dir${path.sep}pluginId_versions.json`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('getVersionedDocsDirPath', () => {
|
describe('getVersionedDocsDirPath', () => {
|
||||||
|
it('works', () => {
|
||||||
expect(getVersionedDocsDirPath('someSiteDir', DEFAULT_PLUGIN_ID)).toBe(
|
expect(getVersionedDocsDirPath('someSiteDir', DEFAULT_PLUGIN_ID)).toBe(
|
||||||
`someSiteDir${path.sep}versioned_docs`,
|
`someSiteDir${path.sep}versioned_docs`,
|
||||||
);
|
);
|
||||||
|
@ -44,8 +46,10 @@ describe('version paths', () => {
|
||||||
`otherSite${path.sep}dir${path.sep}pluginId_versioned_docs`,
|
`otherSite${path.sep}dir${path.sep}pluginId_versioned_docs`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('getVersionedSidebarsDirPath', () => {
|
describe('getVersionedSidebarsDirPath', () => {
|
||||||
|
it('works', () => {
|
||||||
expect(getVersionedSidebarsDirPath('someSiteDir', DEFAULT_PLUGIN_ID)).toBe(
|
expect(getVersionedSidebarsDirPath('someSiteDir', DEFAULT_PLUGIN_ID)).toBe(
|
||||||
`someSiteDir${path.sep}versioned_sidebars`,
|
`someSiteDir${path.sep}versioned_sidebars`,
|
||||||
);
|
);
|
||||||
|
@ -55,7 +59,8 @@ describe('version paths', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('simple site', () => {
|
describe('readVersionsMetadata', () => {
|
||||||
|
describe('simple site', () => {
|
||||||
async function loadSite() {
|
async function loadSite() {
|
||||||
const simpleSiteDir = path.resolve(
|
const simpleSiteDir = path.resolve(
|
||||||
path.join(__dirname, '__fixtures__', 'simple-site'),
|
path.join(__dirname, '__fixtures__', 'simple-site'),
|
||||||
|
@ -90,7 +95,7 @@ describe('simple site', () => {
|
||||||
return {simpleSiteDir, defaultOptions, defaultContext, vCurrent};
|
return {simpleSiteDir, defaultOptions, defaultContext, vCurrent};
|
||||||
}
|
}
|
||||||
|
|
||||||
it('readVersionsMetadata simple site', async () => {
|
it('works', async () => {
|
||||||
const {defaultOptions, defaultContext, vCurrent} = await loadSite();
|
const {defaultOptions, defaultContext, vCurrent} = await loadSite();
|
||||||
|
|
||||||
const versionsMetadata = await readVersionsMetadata({
|
const versionsMetadata = await readVersionsMetadata({
|
||||||
|
@ -101,7 +106,7 @@ describe('simple site', () => {
|
||||||
expect(versionsMetadata).toEqual([vCurrent]);
|
expect(versionsMetadata).toEqual([vCurrent]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata simple site with base url', async () => {
|
it('works with base url', async () => {
|
||||||
const {defaultOptions, defaultContext, vCurrent} = await loadSite();
|
const {defaultOptions, defaultContext, vCurrent} = await loadSite();
|
||||||
|
|
||||||
const versionsMetadata = await readVersionsMetadata({
|
const versionsMetadata = await readVersionsMetadata({
|
||||||
|
@ -121,7 +126,7 @@ describe('simple site', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata simple site with current version config', async () => {
|
it('works with current version config', async () => {
|
||||||
const {defaultOptions, defaultContext, vCurrent} = await loadSite();
|
const {defaultOptions, defaultContext, vCurrent} = await loadSite();
|
||||||
|
|
||||||
const versionsMetadata = await readVersionsMetadata({
|
const versionsMetadata = await readVersionsMetadata({
|
||||||
|
@ -154,7 +159,7 @@ describe('simple site', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata simple site with unknown lastVersion should throw', async () => {
|
it('throws with unknown lastVersion', async () => {
|
||||||
const {defaultOptions, defaultContext} = await loadSite();
|
const {defaultOptions, defaultContext} = await loadSite();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -167,7 +172,7 @@ describe('simple site', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata simple site with unknown version configurations should throw', async () => {
|
it('throws with unknown version configurations', async () => {
|
||||||
const {defaultOptions, defaultContext} = await loadSite();
|
const {defaultOptions, defaultContext} = await loadSite();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -187,7 +192,7 @@ describe('simple site', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata simple site with disableVersioning while single version should throw', async () => {
|
it('throws with disableVersioning while single version', async () => {
|
||||||
const {defaultOptions, defaultContext} = await loadSite();
|
const {defaultOptions, defaultContext} = await loadSite();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -200,7 +205,7 @@ describe('simple site', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata simple site without including current version should throw', async () => {
|
it('throws without including current version', async () => {
|
||||||
const {defaultOptions, defaultContext} = await loadSite();
|
const {defaultOptions, defaultContext} = await loadSite();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -212,9 +217,9 @@ describe('simple site', () => {
|
||||||
`"It is not possible to use docs without any version. Please check the configuration of these options: \\"includeCurrentVersion: false\\", \\"disableVersioning: false\\"."`,
|
`"It is not possible to use docs without any version. Please check the configuration of these options: \\"includeCurrentVersion: false\\", \\"disableVersioning: false\\"."`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('versioned site, pluginId=default', () => {
|
describe('versioned site, pluginId=default', () => {
|
||||||
async function loadSite() {
|
async function loadSite() {
|
||||||
const versionedSiteDir = path.resolve(
|
const versionedSiteDir = path.resolve(
|
||||||
path.join(__dirname, '__fixtures__', 'versioned-site'),
|
path.join(__dirname, '__fixtures__', 'versioned-site'),
|
||||||
|
@ -249,7 +254,10 @@ describe('versioned site, pluginId=default', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const v101: VersionMetadata = {
|
const v101: VersionMetadata = {
|
||||||
contentPath: path.join(versionedSiteDir, 'versioned_docs/version-1.0.1'),
|
contentPath: path.join(
|
||||||
|
versionedSiteDir,
|
||||||
|
'versioned_docs/version-1.0.1',
|
||||||
|
),
|
||||||
contentPathLocalized: path.join(
|
contentPathLocalized: path.join(
|
||||||
versionedSiteDir,
|
versionedSiteDir,
|
||||||
'i18n/en/docusaurus-plugin-content-docs/version-1.0.1',
|
'i18n/en/docusaurus-plugin-content-docs/version-1.0.1',
|
||||||
|
@ -270,7 +278,10 @@ describe('versioned site, pluginId=default', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const v100: VersionMetadata = {
|
const v100: VersionMetadata = {
|
||||||
contentPath: path.join(versionedSiteDir, 'versioned_docs/version-1.0.0'),
|
contentPath: path.join(
|
||||||
|
versionedSiteDir,
|
||||||
|
'versioned_docs/version-1.0.0',
|
||||||
|
),
|
||||||
contentPathLocalized: path.join(
|
contentPathLocalized: path.join(
|
||||||
versionedSiteDir,
|
versionedSiteDir,
|
||||||
'i18n/en/docusaurus-plugin-content-docs/version-1.0.0',
|
'i18n/en/docusaurus-plugin-content-docs/version-1.0.0',
|
||||||
|
@ -325,7 +336,7 @@ describe('versioned site, pluginId=default', () => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
it('readVersionsMetadata versioned site', async () => {
|
it('works', async () => {
|
||||||
const {defaultOptions, defaultContext, vCurrent, v101, v100, vWithSlugs} =
|
const {defaultOptions, defaultContext, vCurrent, v101, v100, vWithSlugs} =
|
||||||
await loadSite();
|
await loadSite();
|
||||||
|
|
||||||
|
@ -337,7 +348,7 @@ describe('versioned site, pluginId=default', () => {
|
||||||
expect(versionsMetadata).toEqual([vCurrent, v101, v100, vWithSlugs]);
|
expect(versionsMetadata).toEqual([vCurrent, v101, v100, vWithSlugs]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata versioned site with includeCurrentVersion=false', async () => {
|
it('works with includeCurrentVersion=false', async () => {
|
||||||
const {defaultOptions, defaultContext, v101, v100, vWithSlugs} =
|
const {defaultOptions, defaultContext, v101, v100, vWithSlugs} =
|
||||||
await loadSite();
|
await loadSite();
|
||||||
|
|
||||||
|
@ -354,7 +365,7 @@ describe('versioned site, pluginId=default', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata versioned site with version options', async () => {
|
it('works with version options', async () => {
|
||||||
const {defaultOptions, defaultContext, vCurrent, v101, v100, vWithSlugs} =
|
const {defaultOptions, defaultContext, vCurrent, v101, v100, vWithSlugs} =
|
||||||
await loadSite();
|
await loadSite();
|
||||||
|
|
||||||
|
@ -408,7 +419,7 @@ describe('versioned site, pluginId=default', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata versioned site with editUrl', async () => {
|
it('works with editUrl', async () => {
|
||||||
const {defaultOptions, defaultContext, vCurrent, v101, v100, vWithSlugs} =
|
const {defaultOptions, defaultContext, vCurrent, v101, v100, vWithSlugs} =
|
||||||
await loadSite();
|
await loadSite();
|
||||||
|
|
||||||
|
@ -452,7 +463,7 @@ describe('versioned site, pluginId=default', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata versioned site with editUrl and editCurrentVersion=true', async () => {
|
it('works with editUrl and editCurrentVersion=true', async () => {
|
||||||
const {defaultOptions, defaultContext, vCurrent, v101, v100, vWithSlugs} =
|
const {defaultOptions, defaultContext, vCurrent, v101, v100, vWithSlugs} =
|
||||||
await loadSite();
|
await loadSite();
|
||||||
|
|
||||||
|
@ -497,8 +508,9 @@ describe('versioned site, pluginId=default', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata versioned site with onlyIncludeVersions option', async () => {
|
it('works with onlyIncludeVersions option', async () => {
|
||||||
const {defaultOptions, defaultContext, v101, vWithSlugs} = await loadSite();
|
const {defaultOptions, defaultContext, v101, vWithSlugs} =
|
||||||
|
await loadSite();
|
||||||
|
|
||||||
const versionsMetadata = await readVersionsMetadata({
|
const versionsMetadata = await readVersionsMetadata({
|
||||||
options: {
|
options: {
|
||||||
|
@ -512,7 +524,7 @@ describe('versioned site, pluginId=default', () => {
|
||||||
expect(versionsMetadata).toEqual([v101, vWithSlugs]);
|
expect(versionsMetadata).toEqual([v101, vWithSlugs]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata versioned site with disableVersioning', async () => {
|
it('works with disableVersioning', async () => {
|
||||||
const {defaultOptions, defaultContext, vCurrent} = await loadSite();
|
const {defaultOptions, defaultContext, vCurrent} = await loadSite();
|
||||||
|
|
||||||
const versionsMetadata = await readVersionsMetadata({
|
const versionsMetadata = await readVersionsMetadata({
|
||||||
|
@ -533,7 +545,7 @@ describe('versioned site, pluginId=default', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata versioned site with all versions disabled', async () => {
|
it('throws with all versions disabled', async () => {
|
||||||
const {defaultOptions, defaultContext} = await loadSite();
|
const {defaultOptions, defaultContext} = await loadSite();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -550,7 +562,7 @@ describe('versioned site, pluginId=default', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata versioned site with empty onlyIncludeVersions', async () => {
|
it('throws with empty onlyIncludeVersions', async () => {
|
||||||
const {defaultOptions, defaultContext} = await loadSite();
|
const {defaultOptions, defaultContext} = await loadSite();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -566,7 +578,7 @@ describe('versioned site, pluginId=default', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata versioned site with unknown versions in onlyIncludeVersions', async () => {
|
it('throws with unknown versions in onlyIncludeVersions', async () => {
|
||||||
const {defaultOptions, defaultContext} = await loadSite();
|
const {defaultOptions, defaultContext} = await loadSite();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -582,7 +594,7 @@ describe('versioned site, pluginId=default', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata versioned site with lastVersion not in onlyIncludeVersions', async () => {
|
it('throws with lastVersion not in onlyIncludeVersions', async () => {
|
||||||
const {defaultOptions, defaultContext} = await loadSite();
|
const {defaultOptions, defaultContext} = await loadSite();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -599,10 +611,11 @@ describe('versioned site, pluginId=default', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata versioned site with invalid versions.json file', async () => {
|
it('throws with invalid versions.json file', async () => {
|
||||||
const {defaultOptions, defaultContext} = await loadSite();
|
const {defaultOptions, defaultContext} = await loadSite();
|
||||||
|
|
||||||
const mock = jest.spyOn(JSON, 'parse').mockImplementationOnce(() => ({
|
const jsonMock = jest.spyOn(JSON, 'parse');
|
||||||
|
jsonMock.mockImplementationOnce(() => ({
|
||||||
invalid: 'json',
|
invalid: 'json',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -612,13 +625,33 @@ describe('versioned site, pluginId=default', () => {
|
||||||
context: defaultContext,
|
context: defaultContext,
|
||||||
}),
|
}),
|
||||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
`"The versions file should contain an array of versions! Found content: {\\"invalid\\":\\"json\\"}"`,
|
`"The versions file should contain an array of version names! Found content: {\\"invalid\\":\\"json\\"}"`,
|
||||||
);
|
);
|
||||||
mock.mockRestore();
|
jsonMock.mockImplementationOnce(() => [1.1]);
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('versioned site, pluginId=community', () => {
|
await expect(
|
||||||
|
readVersionsMetadata({
|
||||||
|
options: defaultOptions,
|
||||||
|
context: defaultContext,
|
||||||
|
}),
|
||||||
|
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"Versions should be strings. Found type \\"number\\" for version \\"1.1\\"."`,
|
||||||
|
);
|
||||||
|
jsonMock.mockImplementationOnce(() => [' ']);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
readVersionsMetadata({
|
||||||
|
options: defaultOptions,
|
||||||
|
context: defaultContext,
|
||||||
|
}),
|
||||||
|
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"Invalid version \\" \\"."`,
|
||||||
|
);
|
||||||
|
jsonMock.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('versioned site, pluginId=community', () => {
|
||||||
async function loadSite() {
|
async function loadSite() {
|
||||||
const versionedSiteDir = path.resolve(
|
const versionedSiteDir = path.resolve(
|
||||||
path.join(__dirname, '__fixtures__', 'versioned-site'),
|
path.join(__dirname, '__fixtures__', 'versioned-site'),
|
||||||
|
@ -681,7 +714,7 @@ describe('versioned site, pluginId=community', () => {
|
||||||
return {versionedSiteDir, defaultOptions, defaultContext, vCurrent, v100};
|
return {versionedSiteDir, defaultOptions, defaultContext, vCurrent, v100};
|
||||||
}
|
}
|
||||||
|
|
||||||
it('readVersionsMetadata versioned site (community)', async () => {
|
it('works', async () => {
|
||||||
const {defaultOptions, defaultContext, vCurrent, v100} = await loadSite();
|
const {defaultOptions, defaultContext, vCurrent, v100} = await loadSite();
|
||||||
|
|
||||||
const versionsMetadata = await readVersionsMetadata({
|
const versionsMetadata = await readVersionsMetadata({
|
||||||
|
@ -692,7 +725,7 @@ describe('versioned site, pluginId=community', () => {
|
||||||
expect(versionsMetadata).toEqual([vCurrent, v100]);
|
expect(versionsMetadata).toEqual([vCurrent, v100]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata versioned site (community) with includeCurrentVersion=false', async () => {
|
it('works with includeCurrentVersion=false', async () => {
|
||||||
const {defaultOptions, defaultContext, v100} = await loadSite();
|
const {defaultOptions, defaultContext, v100} = await loadSite();
|
||||||
|
|
||||||
const versionsMetadata = await readVersionsMetadata({
|
const versionsMetadata = await readVersionsMetadata({
|
||||||
|
@ -706,7 +739,7 @@ describe('versioned site, pluginId=community', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata versioned site (community) with disableVersioning', async () => {
|
it('works with disableVersioning', async () => {
|
||||||
const {defaultOptions, defaultContext, vCurrent} = await loadSite();
|
const {defaultOptions, defaultContext, vCurrent} = await loadSite();
|
||||||
|
|
||||||
const versionsMetadata = await readVersionsMetadata({
|
const versionsMetadata = await readVersionsMetadata({
|
||||||
|
@ -727,7 +760,7 @@ describe('versioned site, pluginId=community', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('readVersionsMetadata versioned site (community) with all versions disabled', async () => {
|
it('throws with all versions disabled', async () => {
|
||||||
const {defaultOptions, defaultContext} = await loadSite();
|
const {defaultOptions, defaultContext} = await loadSite();
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -743,4 +776,5 @@ describe('versioned site, pluginId=community', () => {
|
||||||
`"It is not possible to use docs without any version. Please check the configuration of these options: \\"includeCurrentVersion: false\\", \\"disableVersioning: true\\"."`,
|
`"It is not possible to use docs without any version. Please check the configuration of these options: \\"includeCurrentVersion: false\\", \\"disableVersioning: true\\"."`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -391,6 +391,10 @@ export const isCategoryIndex: CategoryIndexMatcher = ({
|
||||||
return eligibleDocIndexNames.includes(fileName.toLowerCase());
|
return eligibleDocIndexNames.includes(fileName.toLowerCase());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `guides/sidebar/autogenerated.md` ->
|
||||||
|
* `'autogenerated', '.md', ['sidebar', 'guides']`
|
||||||
|
*/
|
||||||
export function toCategoryIndexMatcherParam({
|
export function toCategoryIndexMatcherParam({
|
||||||
source,
|
source,
|
||||||
sourceDirName,
|
sourceDirName,
|
||||||
|
@ -406,28 +410,6 @@ export function toCategoryIndexMatcherParam({
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* `guides/sidebar/autogenerated.md` ->
|
|
||||||
* `'autogenerated', '.md', ['sidebar', 'guides']`
|
|
||||||
*/
|
|
||||||
export function splitPath(str: string): {
|
|
||||||
/**
|
|
||||||
* The list of directories, from lowest level to highest.
|
|
||||||
* If there's no dir name, directories is ['.']
|
|
||||||
*/
|
|
||||||
directories: string[];
|
|
||||||
/** The file name, without extension */
|
|
||||||
fileName: string;
|
|
||||||
/** The extension, with a leading dot */
|
|
||||||
extension: string;
|
|
||||||
} {
|
|
||||||
return {
|
|
||||||
fileName: path.parse(str).name,
|
|
||||||
extension: path.parse(str).ext,
|
|
||||||
directories: path.dirname(str).split(path.sep).reverse(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return both doc ids
|
// Return both doc ids
|
||||||
// TODO legacy retro-compatibility due to old versioned sidebars using
|
// TODO legacy retro-compatibility due to old versioned sidebars using
|
||||||
// versioned doc ids ("id" should be removed & "versionedId" should be renamed
|
// versioned doc ids ("id" should be removed & "versionedId" should be renamed
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import {normalizeUrl} from '@docusaurus/utils';
|
|
||||||
import type {Sidebars} from './sidebars/types';
|
import type {Sidebars} from './sidebars/types';
|
||||||
import {createSidebarsUtils} from './sidebars/utils';
|
import {createSidebarsUtils} from './sidebars/utils';
|
||||||
import type {
|
import type {
|
||||||
|
@ -20,7 +19,7 @@ import type {
|
||||||
GlobalDoc,
|
GlobalDoc,
|
||||||
} from '@docusaurus/plugin-content-docs/client';
|
} from '@docusaurus/plugin-content-docs/client';
|
||||||
|
|
||||||
export function toGlobalDataDoc(doc: DocMetadata): GlobalDoc {
|
function toGlobalDataDoc(doc: DocMetadata): GlobalDoc {
|
||||||
return {
|
return {
|
||||||
id: doc.unversionedId,
|
id: doc.unversionedId,
|
||||||
path: doc.permalink,
|
path: doc.permalink,
|
||||||
|
@ -28,7 +27,7 @@ export function toGlobalDataDoc(doc: DocMetadata): GlobalDoc {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toGlobalDataGeneratedIndex(
|
function toGlobalDataGeneratedIndex(
|
||||||
doc: CategoryGeneratedIndexMetadata,
|
doc: CategoryGeneratedIndexMetadata,
|
||||||
): GlobalDoc {
|
): GlobalDoc {
|
||||||
return {
|
return {
|
||||||
|
@ -38,7 +37,7 @@ export function toGlobalDataGeneratedIndex(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toGlobalSidebars(
|
function toGlobalSidebars(
|
||||||
sidebars: Sidebars,
|
sidebars: Sidebars,
|
||||||
version: LoadedVersion,
|
version: LoadedVersion,
|
||||||
): Record<string, GlobalSidebar> {
|
): Record<string, GlobalSidebar> {
|
||||||
|
@ -52,7 +51,7 @@ export function toGlobalSidebars(
|
||||||
link: {
|
link: {
|
||||||
path:
|
path:
|
||||||
firstLink.type === 'generated-index'
|
firstLink.type === 'generated-index'
|
||||||
? normalizeUrl([version.versionPath, firstLink.slug])
|
? firstLink.permalink
|
||||||
: version.docs.find(
|
: version.docs.find(
|
||||||
(doc) =>
|
(doc) =>
|
||||||
doc.id === firstLink.id || doc.unversionedId === firstLink.id,
|
doc.id === firstLink.id || doc.unversionedId === firstLink.id,
|
||||||
|
|
|
@ -18,8 +18,14 @@ declare module '@docusaurus/plugin-content-docs' {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CategoryIndexMatcherParam = {
|
export type CategoryIndexMatcherParam = {
|
||||||
|
/** The file name, without extension */
|
||||||
fileName: string;
|
fileName: string;
|
||||||
|
/**
|
||||||
|
* The list of directories, from lowest level to highest.
|
||||||
|
* If there's no dir name, directories is ['.']
|
||||||
|
*/
|
||||||
directories: string[];
|
directories: string[];
|
||||||
|
/** The extension, with a leading dot */
|
||||||
extension: string;
|
extension: string;
|
||||||
};
|
};
|
||||||
export type CategoryIndexMatcher = (
|
export type CategoryIndexMatcher = (
|
||||||
|
|
|
@ -111,7 +111,7 @@ describe('createSidebarsUtils', () => {
|
||||||
link: {
|
link: {
|
||||||
type: 'generated-index',
|
type: 'generated-index',
|
||||||
slug: '/s4-category-slug',
|
slug: '/s4-category-slug',
|
||||||
permalink: '/s4-category-permalink',
|
permalink: '/s4-category-slug',
|
||||||
},
|
},
|
||||||
items: [
|
items: [
|
||||||
{type: 'doc', id: 'doc8'},
|
{type: 'doc', id: 'doc8'},
|
||||||
|
@ -291,7 +291,7 @@ describe('createSidebarsUtils', () => {
|
||||||
});
|
});
|
||||||
expect(getFirstLink('sidebar4')).toEqual({
|
expect(getFirstLink('sidebar4')).toEqual({
|
||||||
type: 'generated-index',
|
type: 'generated-index',
|
||||||
slug: '/s4-category-slug',
|
permalink: '/s4-category-slug',
|
||||||
label: 'S4 Category',
|
label: 'S4 Category',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -139,6 +139,11 @@ export type SidebarsUtils = {
|
||||||
getCategoryGeneratedIndexNavigation: (
|
getCategoryGeneratedIndexNavigation: (
|
||||||
categoryGeneratedIndexPermalink: string,
|
categoryGeneratedIndexPermalink: string,
|
||||||
) => SidebarNavigation;
|
) => SidebarNavigation;
|
||||||
|
/**
|
||||||
|
* This function may return undefined. This is usually a user mistake, because
|
||||||
|
* it means this sidebar will never be displayed; however, we can still use
|
||||||
|
* `displayed_sidebar` to make it displayed. Pretty weird but valid use-case
|
||||||
|
*/
|
||||||
getFirstLink: (sidebarId: string) =>
|
getFirstLink: (sidebarId: string) =>
|
||||||
| {
|
| {
|
||||||
type: 'doc';
|
type: 'doc';
|
||||||
|
@ -147,7 +152,7 @@ export type SidebarsUtils = {
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'generated-index';
|
type: 'generated-index';
|
||||||
slug: string;
|
permalink: string;
|
||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
|
@ -295,7 +300,7 @@ Available document ids are:
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'generated-index';
|
type: 'generated-index';
|
||||||
slug: string;
|
permalink: string;
|
||||||
label: string;
|
label: string;
|
||||||
}
|
}
|
||||||
| undefined {
|
| undefined {
|
||||||
|
@ -316,7 +321,7 @@ Available document ids are:
|
||||||
} else if (item.link?.type === 'generated-index') {
|
} else if (item.link?.type === 'generated-index') {
|
||||||
return {
|
return {
|
||||||
type: 'generated-index',
|
type: 'generated-index',
|
||||||
slug: item.link.slug,
|
permalink: item.link.permalink,
|
||||||
label: item.label,
|
label: item.label,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,12 +63,11 @@ export default function getSlug({
|
||||||
function ensureValidSlug(slug: string): string {
|
function ensureValidSlug(slug: string): string {
|
||||||
if (!isValidPathname(slug)) {
|
if (!isValidPathname(slug)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`We couldn't compute a valid slug for document with id "${baseID}" in "${sourceDirName}" directory.
|
`We couldn't compute a valid slug for document with ID "${baseID}" in "${sourceDirName}" directory.
|
||||||
The slug we computed looks invalid: ${slug}.
|
The slug we computed looks invalid: ${slug}.
|
||||||
Maybe your slug front matter is incorrect or you use weird chars in the file path?
|
Maybe your slug front matter is incorrect or there are special characters in the file path?
|
||||||
By using the slug front matter, you should be able to fix this error, by using the slug of your choice:
|
By using front matter to set a custom slug, you should be able to fix this error:
|
||||||
|
|
||||||
Example =>
|
|
||||||
---
|
---
|
||||||
slug: /my/customDocPath
|
slug: /my/customDocPath
|
||||||
---
|
---
|
||||||
|
|
|
@ -274,10 +274,7 @@ function translateVersion(
|
||||||
translationFiles: Record<string, TranslationFile>,
|
translationFiles: Record<string, TranslationFile>,
|
||||||
): LoadedVersion {
|
): LoadedVersion {
|
||||||
const versionTranslations =
|
const versionTranslations =
|
||||||
translationFiles[getVersionFileName(version.versionName)]?.content;
|
translationFiles[getVersionFileName(version.versionName)]!.content;
|
||||||
if (!versionTranslations) {
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
...version,
|
...version,
|
||||||
versionLabel:
|
versionLabel:
|
||||||
|
|
|
@ -74,9 +74,9 @@ function ensureValidVersionString(version: unknown): asserts version is string {
|
||||||
function ensureValidVersionArray(
|
function ensureValidVersionArray(
|
||||||
versionArray: unknown,
|
versionArray: unknown,
|
||||||
): asserts versionArray is string[] {
|
): asserts versionArray is string[] {
|
||||||
if (!(versionArray instanceof Array)) {
|
if (!Array.isArray(versionArray)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`The versions file should contain an array of versions! Found content: ${JSON.stringify(
|
`The versions file should contain an array of version names! Found content: ${JSON.stringify(
|
||||||
versionArray,
|
versionArray,
|
||||||
)}`,
|
)}`,
|
||||||
);
|
);
|
||||||
|
|
|
@ -218,7 +218,7 @@ export default function SearchPage(): JSX.Element {
|
||||||
algoliaHelper.on(
|
algoliaHelper.on(
|
||||||
'result',
|
'result',
|
||||||
({results: {query, hits, page, nbHits, nbPages}}) => {
|
({results: {query, hits, page, nbHits, nbPages}}) => {
|
||||||
if (query === '' || !(hits instanceof Array)) {
|
if (query === '' || !Array.isArray(hits)) {
|
||||||
searchResultStateDispatcher({type: 'reset'});
|
searchResultStateDispatcher({type: 'reset'});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,15 @@ import {jest} from '@jest/globals';
|
||||||
import normalizeLocation from '../normalizeLocation';
|
import normalizeLocation from '../normalizeLocation';
|
||||||
|
|
||||||
describe('normalizeLocation', () => {
|
describe('normalizeLocation', () => {
|
||||||
it('rewrite locations with index.html', () => {
|
it('rewrites locations with index.html', () => {
|
||||||
|
expect(
|
||||||
|
normalizeLocation({
|
||||||
|
pathname: '/index.html',
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
pathname: '/',
|
||||||
|
});
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
normalizeLocation({
|
normalizeLocation({
|
||||||
pathname: '/docs/introduction/index.html',
|
pathname: '/docs/introduction/index.html',
|
||||||
|
@ -35,7 +43,7 @@ describe('normalizeLocation', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('untouched pathnames', () => {
|
it('leaves pathnames untouched', () => {
|
||||||
const replaceMock = jest.spyOn(String.prototype, 'replace');
|
const replaceMock = jest.spyOn(String.prototype, 'replace');
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
|
|
@ -89,12 +89,12 @@ export function interpolate<Str extends string, Value extends ReactNode>(
|
||||||
export default function Interpolate<Str extends string>({
|
export default function Interpolate<Str extends string>({
|
||||||
children,
|
children,
|
||||||
values,
|
values,
|
||||||
}: InterpolateProps<Str>): ReactNode {
|
}: InterpolateProps<Str>): JSX.Element {
|
||||||
if (typeof children !== 'string') {
|
if (typeof children !== 'string') {
|
||||||
console.warn('Illegal <Interpolate> children', children);
|
console.warn('Illegal <Interpolate> children', children);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'The Docusaurus <Interpolate> component only accept simple string values',
|
'The Docusaurus <Interpolate> component only accept simple string values',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return interpolate(children, values);
|
return <>{interpolate(children, values)}</>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {ReactNode} from 'react';
|
import React from 'react';
|
||||||
import {interpolate, type InterpolateValues} from '@docusaurus/Interpolate';
|
import {interpolate, type InterpolateValues} from '@docusaurus/Interpolate';
|
||||||
import type {TranslateParam, TranslateProps} from '@docusaurus/Translate';
|
import type {TranslateParam, TranslateProps} from '@docusaurus/Translate';
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ export default function Translate<Str extends string>({
|
||||||
children,
|
children,
|
||||||
id,
|
id,
|
||||||
values,
|
values,
|
||||||
}: TranslateProps<Str>): ReactNode {
|
}: TranslateProps<Str>): JSX.Element {
|
||||||
if (children && typeof children !== 'string') {
|
if (children && typeof children !== 'string') {
|
||||||
console.warn('Illegal <Translate> children', children);
|
console.warn('Illegal <Translate> children', children);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -55,5 +55,5 @@ export default function Translate<Str extends string>({
|
||||||
}
|
}
|
||||||
|
|
||||||
const localizedMessage: string = getLocalizedMessage({message: children, id});
|
const localizedMessage: string = getLocalizedMessage({message: children, id});
|
||||||
return interpolate(localizedMessage, values);
|
return <>{interpolate(localizedMessage, values)}</>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,46 +5,55 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {jest} from '@jest/globals';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import BrowserOnly from '../BrowserOnly';
|
import BrowserOnly from '../BrowserOnly';
|
||||||
|
import {Context} from '../browserContext';
|
||||||
|
|
||||||
jest.mock('@docusaurus/useIsBrowser', () => () => true);
|
describe('<BrowserOnly>', () => {
|
||||||
|
|
||||||
describe('BrowserOnly', () => {
|
|
||||||
it('rejects react element children', () => {
|
it('rejects react element children', () => {
|
||||||
process.env.NODE_ENV = 'development';
|
process.env.NODE_ENV = 'development';
|
||||||
expect(() => {
|
expect(() =>
|
||||||
renderer.create(
|
renderer
|
||||||
|
.create(
|
||||||
|
<Context.Provider value>
|
||||||
<BrowserOnly>
|
<BrowserOnly>
|
||||||
{/* @ts-expect-error test */}
|
{/* @ts-expect-error test */}
|
||||||
<span>{window.location.href}</span>
|
<span>{window.location.href}</span>
|
||||||
</BrowserOnly>,
|
</BrowserOnly>
|
||||||
);
|
</Context.Provider>,
|
||||||
}).toThrowErrorMatchingInlineSnapshot(`
|
)
|
||||||
|
.toJSON(),
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(`
|
||||||
"Docusaurus error: The children of <BrowserOnly> must be a \\"render function\\", e.g. <BrowserOnly>{() => <span>{window.location.href}</span>}</BrowserOnly>.
|
"Docusaurus error: The children of <BrowserOnly> must be a \\"render function\\", e.g. <BrowserOnly>{() => <span>{window.location.href}</span>}</BrowserOnly>.
|
||||||
Current type: React element"
|
Current type: React element"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects string children', () => {
|
it('rejects string children', () => {
|
||||||
|
process.env.NODE_ENV = 'development';
|
||||||
expect(() => {
|
expect(() => {
|
||||||
renderer.create(
|
renderer.create(
|
||||||
// @ts-expect-error test
|
<Context.Provider value>
|
||||||
<BrowserOnly> </BrowserOnly>,
|
{/* @ts-expect-error test */}
|
||||||
|
<BrowserOnly> </BrowserOnly>
|
||||||
|
</Context.Provider>,
|
||||||
);
|
);
|
||||||
}).toThrowErrorMatchingInlineSnapshot(`
|
}).toThrowErrorMatchingInlineSnapshot(`
|
||||||
"Docusaurus error: The children of <BrowserOnly> must be a \\"render function\\", e.g. <BrowserOnly>{() => <span>{window.location.href}</span>}</BrowserOnly>.
|
"Docusaurus error: The children of <BrowserOnly> must be a \\"render function\\", e.g. <BrowserOnly>{() => <span>{window.location.href}</span>}</BrowserOnly>.
|
||||||
Current type: string"
|
Current type: string"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accepts valid children', () => {
|
it('accepts valid children', () => {
|
||||||
expect(
|
expect(
|
||||||
renderer
|
renderer
|
||||||
.create(
|
.create(
|
||||||
|
<Context.Provider value>
|
||||||
<BrowserOnly fallback={<span>Loading</span>}>
|
<BrowserOnly fallback={<span>Loading</span>}>
|
||||||
{() => <span>{window.location.href}</span>}
|
{() => <span>{window.location.href}</span>}
|
||||||
</BrowserOnly>,
|
</BrowserOnly>
|
||||||
|
</Context.Provider>,
|
||||||
)
|
)
|
||||||
.toJSON(),
|
.toJSON(),
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
|
@ -53,4 +62,36 @@ describe('BrowserOnly', () => {
|
||||||
</span>
|
</span>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns fallback when not in browser', () => {
|
||||||
|
expect(
|
||||||
|
renderer
|
||||||
|
.create(
|
||||||
|
<Context.Provider value={false}>
|
||||||
|
<BrowserOnly fallback={<span>Loading</span>}>
|
||||||
|
{() => <span>{window.location.href}</span>}
|
||||||
|
</BrowserOnly>
|
||||||
|
</Context.Provider>,
|
||||||
|
)
|
||||||
|
.toJSON(),
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
<span>
|
||||||
|
Loading
|
||||||
|
</span>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gracefully falls back', () => {
|
||||||
|
expect(
|
||||||
|
renderer
|
||||||
|
.create(
|
||||||
|
<Context.Provider value={false}>
|
||||||
|
<BrowserOnly>
|
||||||
|
{() => <span>{window.location.href}</span>}
|
||||||
|
</BrowserOnly>
|
||||||
|
</Context.Provider>,
|
||||||
|
)
|
||||||
|
.toJSON(),
|
||||||
|
).toMatchInlineSnapshot(`null`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,9 +6,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {interpolate} from '../Interpolate';
|
import renderer from 'react-test-renderer';
|
||||||
|
import Interpolate, {interpolate} from '../Interpolate';
|
||||||
|
|
||||||
describe('Interpolate', () => {
|
describe('interpolate', () => {
|
||||||
it('without placeholders', () => {
|
it('without placeholders', () => {
|
||||||
const text = 'Hello how are you?';
|
const text = 'Hello how are you?';
|
||||||
expect(interpolate(text)).toEqual(text);
|
expect(interpolate(text)).toEqual(text);
|
||||||
|
@ -86,3 +87,50 @@ describe('Interpolate', () => {
|
||||||
expect(interpolate(text, values)).toMatchSnapshot();
|
expect(interpolate(text, values)).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('<Interpolate>', () => {
|
||||||
|
it('without placeholders', () => {
|
||||||
|
const text = 'Hello how are you?';
|
||||||
|
expect(renderer.create(<Interpolate>{text}</Interpolate>).toJSON()).toEqual(
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('placeholders with string values', () => {
|
||||||
|
const text = 'Hello {name} how are you {day}?';
|
||||||
|
const values = {name: 'Sébastien', day: 'today'};
|
||||||
|
expect(
|
||||||
|
renderer
|
||||||
|
.create(<Interpolate values={values}>{text}</Interpolate>)
|
||||||
|
.toJSON(),
|
||||||
|
).toMatchInlineSnapshot(`"Hello Sébastien how are you today?"`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('acceptance test', () => {
|
||||||
|
const text = 'Hello {name} how are you {day}? Another {unprovidedValue}!';
|
||||||
|
const values = {
|
||||||
|
name: 'Sébastien',
|
||||||
|
day: <span>today</span>,
|
||||||
|
extraUselessValue1: <div>test</div>,
|
||||||
|
extraUselessValue2: 'hi',
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
renderer
|
||||||
|
.create(<Interpolate values={values}>{text}</Interpolate>)
|
||||||
|
.toJSON(),
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects when children is not string', () => {
|
||||||
|
expect(() =>
|
||||||
|
renderer.create(
|
||||||
|
<Interpolate>
|
||||||
|
{/* @ts-expect-error: for test */}
|
||||||
|
<span>aaa</span>
|
||||||
|
</Interpolate>,
|
||||||
|
),
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"The Docusaurus <Interpolate> component only accept simple string values"`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -5,28 +5,75 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {translate} from '../Translate';
|
import React from 'react';
|
||||||
|
import renderer from 'react-test-renderer';
|
||||||
|
import Translate, {translate} from '../Translate';
|
||||||
|
|
||||||
describe('translate', () => {
|
describe('translate', () => {
|
||||||
it('accept id and use it as fallback', () => {
|
it('accepts id and uses it as fallback', () => {
|
||||||
expect(translate({id: 'some-id'})).toBe('some-id');
|
expect(translate({id: 'some-id'})).toBe('some-id');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accept message and use it as fallback', () => {
|
it('accepts message and uses it as fallback', () => {
|
||||||
expect(translate({message: 'some-message'})).toBe('some-message');
|
expect(translate({message: 'some-message'})).toBe('some-message');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accept id+message and use message as fallback', () => {
|
it('accepts id+message and uses message as fallback', () => {
|
||||||
expect(translate({id: 'some-id', message: 'some-message'})).toBe(
|
expect(translate({id: 'some-id', message: 'some-message'})).toBe(
|
||||||
'some-message',
|
'some-message',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reject when no id or message', () => {
|
it('rejects when no id or message', () => {
|
||||||
// TODO tests are not resolving type defs correctly
|
// TODO tests are not resolving type defs correctly. We need to include test
|
||||||
|
// files in a tsconfig file
|
||||||
// @ts-expect-error: TS should protect when both id/message are missing
|
// @ts-expect-error: TS should protect when both id/message are missing
|
||||||
expect(() => translate({})).toThrowErrorMatchingInlineSnapshot(
|
expect(() => translate({})).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"Docusaurus translation declarations must have at least a translation id or a default translation message"`,
|
`"Docusaurus translation declarations must have at least a translation id or a default translation message"`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('<Translate>', () => {
|
||||||
|
it('accepts id and uses it as fallback', () => {
|
||||||
|
expect(renderer.create(<Translate id="some-id" />).toJSON()).toBe(
|
||||||
|
'some-id',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts message and uses it as fallback', () => {
|
||||||
|
expect(renderer.create(<Translate>some-message</Translate>).toJSON()).toBe(
|
||||||
|
'some-message',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts id+message and uses message as fallback', () => {
|
||||||
|
expect(
|
||||||
|
renderer
|
||||||
|
.create(<Translate id="some-id">some-message</Translate>)
|
||||||
|
.toJSON(),
|
||||||
|
).toBe('some-message');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects when no id or message', () => {
|
||||||
|
expect(() =>
|
||||||
|
// @ts-expect-error: TS should protect when both id/message are missing
|
||||||
|
renderer.create(<Translate />),
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"Docusaurus translation declarations must have at least a translation id or a default translation message"`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects when children is not a string', () => {
|
||||||
|
expect(() =>
|
||||||
|
renderer.create(
|
||||||
|
<Translate id="foo">
|
||||||
|
{/* @ts-expect-error: for test */}
|
||||||
|
<span>aaa</span>
|
||||||
|
</Translate>,
|
||||||
|
),
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"The Docusaurus <Translate> component only accept simple string values"`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -1,6 +1,18 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Interpolate acceptance test 1`] = `
|
exports[`<Interpolate> acceptance test 1`] = `
|
||||||
|
Array [
|
||||||
|
"Hello ",
|
||||||
|
"Sébastien",
|
||||||
|
" how are you ",
|
||||||
|
<span>
|
||||||
|
today
|
||||||
|
</span>,
|
||||||
|
"? Another {unprovidedValue}!",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`interpolate acceptance test 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
Hello
|
Hello
|
||||||
|
@ -18,7 +30,7 @@ Array [
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Interpolate placeholders with JSX values 1`] = `
|
exports[`interpolate placeholders with JSX values 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
Hello
|
Hello
|
||||||
|
@ -38,7 +50,7 @@ Array [
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Interpolate placeholders with mixed vales 1`] = `
|
exports[`interpolate placeholders with mixed vales 1`] = `
|
||||||
Array [
|
Array [
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
Hello
|
Hello
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @jest-environment jsdom
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Jest doesn't allow pragma below other comments. https://github.com/facebook/jest/issues/12573
|
||||||
|
// eslint-disable-next-line header/header
|
||||||
|
import React from 'react';
|
||||||
|
import {renderHook} from '@testing-library/react-hooks/server';
|
||||||
|
import {BrowserContextProvider} from '../browserContext';
|
||||||
|
import useIsBrowser from '../useIsBrowser';
|
||||||
|
|
||||||
|
describe('BrowserContextProvider', () => {
|
||||||
|
const {result, hydrate} = renderHook(() => useIsBrowser(), {
|
||||||
|
wrapper: ({children}) => (
|
||||||
|
<BrowserContextProvider>{children}</BrowserContextProvider>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
it('has value false on first render', () => {
|
||||||
|
expect(result.current).toBe(false);
|
||||||
|
});
|
||||||
|
it('has value true on hydration', () => {
|
||||||
|
hydrate();
|
||||||
|
expect(result.current).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -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.
|
||||||
|
*
|
||||||
|
* @jest-environment jsdom
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Jest doesn't allow pragma below other comments. https://github.com/facebook/jest/issues/12573
|
||||||
|
// eslint-disable-next-line header/header
|
||||||
|
import React from 'react';
|
||||||
|
import {renderHook} from '@testing-library/react-hooks/server';
|
||||||
|
import {DocusaurusContextProvider} from '../docusaurusContext';
|
||||||
|
import useDocusaurusContext from '../useDocusaurusContext';
|
||||||
|
|
||||||
|
// This test currently isn't quite useful because the @generated aliases point
|
||||||
|
// to the empty modules. Maybe we can point that to fixtures in the future.
|
||||||
|
describe('DocusaurusContextProvider', () => {
|
||||||
|
const {result, hydrate} = renderHook(() => useDocusaurusContext(), {
|
||||||
|
wrapper: ({children}) => (
|
||||||
|
<DocusaurusContextProvider>{children}</DocusaurusContextProvider>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
const value = result.current;
|
||||||
|
it('returns right value', () => {
|
||||||
|
expect(value).toMatchInlineSnapshot(`
|
||||||
|
Object {
|
||||||
|
"codeTranslations": Object {},
|
||||||
|
"globalData": Object {},
|
||||||
|
"i18n": Object {},
|
||||||
|
"siteConfig": Object {},
|
||||||
|
"siteMetadata": Object {},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('has reference-equal value on hydration', () => {
|
||||||
|
hydrate();
|
||||||
|
expect(result.current).toBe(value);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,122 @@
|
||||||
|
/**
|
||||||
|
* 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 React from 'react';
|
||||||
|
import {renderHook} from '@testing-library/react-hooks';
|
||||||
|
import useGlobalData, {
|
||||||
|
useAllPluginInstancesData,
|
||||||
|
usePluginData,
|
||||||
|
} from '../useGlobalData';
|
||||||
|
import {Context} from '../docusaurusContext';
|
||||||
|
|
||||||
|
describe('useGlobalData', () => {
|
||||||
|
it('returns global data from context', () => {
|
||||||
|
expect(
|
||||||
|
renderHook(() => useGlobalData(), {
|
||||||
|
wrapper: ({children}) => (
|
||||||
|
// eslint-disable-next-line react/jsx-no-constructed-context-values
|
||||||
|
<Context.Provider value={{globalData: {foo: 'bar'}}}>
|
||||||
|
{children}
|
||||||
|
</Context.Provider>
|
||||||
|
),
|
||||||
|
}).result.current,
|
||||||
|
).toEqual({foo: 'bar'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when global data not found', () => {
|
||||||
|
// Can it actually happen?
|
||||||
|
expect(
|
||||||
|
() =>
|
||||||
|
renderHook(() => useGlobalData(), {
|
||||||
|
wrapper: ({children}) => (
|
||||||
|
// eslint-disable-next-line react/jsx-no-constructed-context-values
|
||||||
|
<Context.Provider value={{}}>{children}</Context.Provider>
|
||||||
|
),
|
||||||
|
}).result.current,
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(`"Docusaurus global data not found."`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('useAllPluginInstancesData', () => {
|
||||||
|
it('returns plugin data namespace', () => {
|
||||||
|
expect(
|
||||||
|
renderHook(() => useAllPluginInstancesData('foo'), {
|
||||||
|
wrapper: ({children}) => (
|
||||||
|
<Context.Provider
|
||||||
|
// eslint-disable-next-line react/jsx-no-constructed-context-values
|
||||||
|
value={{globalData: {foo: {default: 'default', bar: 'bar'}}}}>
|
||||||
|
{children}
|
||||||
|
</Context.Provider>
|
||||||
|
),
|
||||||
|
}).result.current,
|
||||||
|
).toEqual({default: 'default', bar: 'bar'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when plugin data not found', () => {
|
||||||
|
expect(
|
||||||
|
() =>
|
||||||
|
renderHook(() => useAllPluginInstancesData('bar'), {
|
||||||
|
wrapper: ({children}) => (
|
||||||
|
<Context.Provider
|
||||||
|
// eslint-disable-next-line react/jsx-no-constructed-context-values
|
||||||
|
value={{globalData: {foo: {default: 'default', bar: 'bar'}}}}>
|
||||||
|
{children}
|
||||||
|
</Context.Provider>
|
||||||
|
),
|
||||||
|
}).result.current,
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"Docusaurus plugin global data not found for \\"bar\\" plugin."`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('usePluginData', () => {
|
||||||
|
it('returns plugin instance data', () => {
|
||||||
|
expect(
|
||||||
|
renderHook(() => usePluginData('foo', 'bar'), {
|
||||||
|
wrapper: ({children}) => (
|
||||||
|
<Context.Provider
|
||||||
|
// eslint-disable-next-line react/jsx-no-constructed-context-values
|
||||||
|
value={{globalData: {foo: {default: 'default', bar: 'bar'}}}}>
|
||||||
|
{children}
|
||||||
|
</Context.Provider>
|
||||||
|
),
|
||||||
|
}).result.current,
|
||||||
|
).toBe('bar');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults to default ID', () => {
|
||||||
|
expect(
|
||||||
|
renderHook(() => usePluginData('foo'), {
|
||||||
|
wrapper: ({children}) => (
|
||||||
|
<Context.Provider
|
||||||
|
// eslint-disable-next-line react/jsx-no-constructed-context-values
|
||||||
|
value={{globalData: {foo: {default: 'default', bar: 'bar'}}}}>
|
||||||
|
{children}
|
||||||
|
</Context.Provider>
|
||||||
|
),
|
||||||
|
}).result.current,
|
||||||
|
).toBe('default');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when plugin instance data not found', () => {
|
||||||
|
expect(
|
||||||
|
() =>
|
||||||
|
renderHook(() => usePluginData('foo', 'baz'), {
|
||||||
|
wrapper: ({children}) => (
|
||||||
|
<Context.Provider
|
||||||
|
// eslint-disable-next-line react/jsx-no-constructed-context-values
|
||||||
|
value={{globalData: {foo: {default: 'default', bar: 'bar'}}}}>
|
||||||
|
{children}
|
||||||
|
</Context.Provider>
|
||||||
|
),
|
||||||
|
}).result.current,
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"Docusaurus plugin global data not found for \\"foo\\" plugin with id \\"baz\\"."`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -18,12 +18,8 @@ export default function normalizeLocation<T extends Location>(location: T): T {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let pathname = location.pathname || '/';
|
const pathname =
|
||||||
pathname = pathname.trim().replace(/\/index\.html$/, '');
|
location.pathname.trim().replace(/\/index\.html$/, '') || '/';
|
||||||
|
|
||||||
if (pathname === '') {
|
|
||||||
pathname = '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
pathnames[location.pathname] = pathname;
|
pathnames[location.pathname] = pathname;
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ export default async function loadConfig(
|
||||||
| (() => Promise<Partial<DocusaurusConfig>>);
|
| (() => Promise<Partial<DocusaurusConfig>>);
|
||||||
|
|
||||||
const loadedConfig =
|
const loadedConfig =
|
||||||
importedConfig instanceof Function
|
typeof importedConfig === 'function'
|
||||||
? await importedConfig()
|
? await importedConfig()
|
||||||
: await importedConfig;
|
: await importedConfig;
|
||||||
|
|
||||||
|
|
24
packages/docusaurus/src/webpack/__tests__/__fixtures__/host.crt
generated
Normal file
24
packages/docusaurus/src/webpack/__tests__/__fixtures__/host.crt
generated
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIID8zCCAtugAwIBAgIUK1U7Oje+GjLlzxNryMDUT72qJZ0wDQYJKoZIhvcNAQEL
|
||||||
|
BQAwgYgxCzAJBgNVBAYTAkNOMREwDwYDVQQIDAhTaGFuZ2hhaTERMA8GA1UEBwwI
|
||||||
|
U2hhbmdoYWkxGDAWBgNVBAoMD0NvbXB1dGVyaXphdGlvbjESMBAGA1UEAwwJSm9z
|
||||||
|
aC1DZW5hMSUwIwYJKoZIhvcNAQkBFhZzaWRhY2hlbjIwMDNAZ21haWwuY29tMB4X
|
||||||
|
DTIyMDMxMjE0MzI0N1oXDTIzMDMxMjE0MzI0N1owgYgxCzAJBgNVBAYTAkNOMREw
|
||||||
|
DwYDVQQIDAhTaGFuZ2hhaTERMA8GA1UEBwwIU2hhbmdoYWkxGDAWBgNVBAoMD0Nv
|
||||||
|
bXB1dGVyaXphdGlvbjESMBAGA1UEAwwJSm9zaC1DZW5hMSUwIwYJKoZIhvcNAQkB
|
||||||
|
FhZzaWRhY2hlbjIwMDNAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
||||||
|
MIIBCgKCAQEA7Cq2QW6rcZAm6MMo97aqkFi9dkXx97fW6vPEt2bgS9O6E+M/wXBI
|
||||||
|
q1Dh3ud8sGP+CiEWa+7uIEwX9pRGyQo0Lkr7qZWSbsDh+RmdkiKUCiIUUTBopBjM
|
||||||
|
jo7XF9KBM609GYoGlKYxv4adPbOMJcK/9VdJPz3NprIA1PHEqInJNnuKMMjBMhNu
|
||||||
|
1MZ7JwING/LYBOJ/Mve08XKAcyDdWBVPe2TOfcKhEmtBTKhnOuUicuAdVtDkN34Z
|
||||||
|
e4ZlifLo7wlQU7NNh7YDOYZz3JXB5QotuqtWkUgfpMSCWG90p4P4LExLzS+2sb7O
|
||||||
|
C/jO0qYcKjaKAKjrA9IIyClF6VP1yFRZywIDAQABo1MwUTAdBgNVHQ4EFgQUNy2X
|
||||||
|
+cLPh17QdR6raPKeoKLIm2QwHwYDVR0jBBgwFoAUNy2X+cLPh17QdR6raPKeoKLI
|
||||||
|
m2QwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAzvyP1QxKR8Ik
|
||||||
|
k7v3dzRl1gKdu6BtRL1zoFXeOjFOCVX9ORxcpCJItuTM4kEbJLhC0uFxn+zQ/Urs
|
||||||
|
JAc56gic4fCIcxlTNPr4VtAEUQKhfGG7XTRs8Cl2Rm7E7FwNiGjdLuiPI+G+ZZbl
|
||||||
|
TYmB5ILGzvI8LAOii17s5wFX84PehZ9gYgcgEvVBaU7lWF3WakR53Msf2bHkjk/r
|
||||||
|
NfaINeBltOwijhzb8pWf0XG2z4olJjg1qTOgr1gNseyTwMAFwFmeXQAoidoZfKya
|
||||||
|
DD+hY1/IgiUXi2pdmO+sMHtRBG5JdOi2cjSOcTx1xkWyb60PpW4uxKhduQPAiZRO
|
||||||
|
266P7J962Q==
|
||||||
|
-----END CERTIFICATE-----
|
27
packages/docusaurus/src/webpack/__tests__/__fixtures__/host.key
generated
Normal file
27
packages/docusaurus/src/webpack/__tests__/__fixtures__/host.key
generated
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEA7Cq2QW6rcZAm6MMo97aqkFi9dkXx97fW6vPEt2bgS9O6E+M/
|
||||||
|
wXBIq1Dh3ud8sGP+CiEWa+7uIEwX9pRGyQo0Lkr7qZWSbsDh+RmdkiKUCiIUUTBo
|
||||||
|
pBjMjo7XF9KBM609GYoGlKYxv4adPbOMJcK/9VdJPz3NprIA1PHEqInJNnuKMMjB
|
||||||
|
MhNu1MZ7JwING/LYBOJ/Mve08XKAcyDdWBVPe2TOfcKhEmtBTKhnOuUicuAdVtDk
|
||||||
|
N34Ze4ZlifLo7wlQU7NNh7YDOYZz3JXB5QotuqtWkUgfpMSCWG90p4P4LExLzS+2
|
||||||
|
sb7OC/jO0qYcKjaKAKjrA9IIyClF6VP1yFRZywIDAQABAoIBAHiHR+LW/2qC3ki2
|
||||||
|
qWba8+udTnxZMCdzzJy2cjQDrf8k/Hd/6B7qFjxQmCXx0GIZdiJnRpEpLKCRFT3D
|
||||||
|
6Ohba8wgepXO/x/FEs7VsuRM/264e9P/t7ff7C3pWn8O8N+Vz3QETF17ADK2GfPO
|
||||||
|
eX0gCmXE+V3sRdOITwJerTYys904bo5CQsDQQENpcuYbZU2IYt9dw9XrTexaFwP1
|
||||||
|
3ssOXCwpaW4kS95a6WQlwCqNTq49zqf3VGA3QG3JEdPPWhG+jEG2L4RxSosvo4wt
|
||||||
|
MYFqeXcS5sz7WOH1gtleGL2i6WKYuLl7Bo/CLokn1tgrXjGvNpeBFvZucC+L246f
|
||||||
|
e7iG+gkCgYEA+CcISFav/uwKNv3Sdp87kVpBAno8cZTiYvB15zAGaXuLyI/OuJNh
|
||||||
|
lcJBhtZSN94T/mgj+gXDafjmRr4i7Q4Pu+KG95JTk1FfWv/974NxbRNrrp+4PFKb
|
||||||
|
wxcM1cHuqq88mUPUX+k0eKPqDcuY6vHBPAV4ji1Wl+VXpREDvhKgAEUCgYEA86Kl
|
||||||
|
xnOf3TWbEaQRJx2mMnRYLyrEEPqEMgHWlzXdWl2E9LJDGGmOEbZLv6uNcx1uWJVP
|
||||||
|
AaoitmQNTl+rSsJY0TwqooX5zvT8po9MXUt8FvButJyYUOJZFTuLtLxFJqAzFipz
|
||||||
|
SaiYTrEBC76uqe/87AVm0wCdJN4ajcptyibaus8CgYEAnXSm3L+kjKxZDuufT4VZ
|
||||||
|
1rDd7ySAldFSlFTfewIOD4BFAc297YAWu1+3FEeJg8l2BkcuDMb7Z5J3Cww6PRBf
|
||||||
|
C2iBGzXNsfw/9Q3ZotBUeFGKUhMmY6BHFVLa4gdb2RG38cgISZM/qAzZxkcZkHo1
|
||||||
|
klAmXpCGEXuEUUiqh0BqJcECgYEAv42Gt0QbUeoetL0BO3blP9AXsWX3Z73/h+3I
|
||||||
|
EXUpRy42JcmuVRhQuf5RCi7QdMyUAJPL3WwuBKcfixpO6+VnvYKHpuadZSlbJ32N
|
||||||
|
NeDufH6nG9vvKdD852O80OohmF/mKqxPnn8u2Nf0EY7ndvcYLV2F3aoi42S5Dfg1
|
||||||
|
X/YyjSMCgYAg2fEisapxje98KZ4TPvOffJRF5PRG4H6UBQvxaWw9oUjVkGM6t10U
|
||||||
|
D6uOCYPkb+l3wBFTNAfScr22EnpW33Q5JOAfHBeE1oEoWGdMgp1C1V9ZQTIkjXyj
|
||||||
|
YE+lrsTFVoyY+dnLcZ4U7syVkzINk10GaAKjGXD0gtrqC+cQy8z1XQ==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
1
packages/docusaurus/src/webpack/__tests__/__fixtures__/invalid.crt
generated
Normal file
1
packages/docusaurus/src/webpack/__tests__/__fixtures__/invalid.crt
generated
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Foo
|
1
packages/docusaurus/src/webpack/__tests__/__fixtures__/invalid.key
generated
Normal file
1
packages/docusaurus/src/webpack/__tests__/__fixtures__/invalid.key
generated
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Foo
|
|
@ -13,7 +13,7 @@ import loadSetup from '../../server/__tests__/testUtils';
|
||||||
|
|
||||||
describe('webpack production config', () => {
|
describe('webpack production config', () => {
|
||||||
it('simple', async () => {
|
it('simple', async () => {
|
||||||
jest.spyOn(console, 'log').mockImplementation();
|
jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
const props = await loadSetup('simple');
|
const props = await loadSetup('simple');
|
||||||
const config = await createServerConfig({props});
|
const config = await createServerConfig({props});
|
||||||
const errors = webpack.validate(config);
|
const errors = webpack.validate(config);
|
||||||
|
@ -21,7 +21,7 @@ describe('webpack production config', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('custom', async () => {
|
it('custom', async () => {
|
||||||
jest.spyOn(console, 'log').mockImplementation();
|
jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
const props = await loadSetup('custom');
|
const props = await loadSetup('custom');
|
||||||
const config = await createServerConfig({props});
|
const config = await createServerConfig({props});
|
||||||
const errors = webpack.validate(config);
|
const errors = webpack.validate(config);
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
getCustomizableJSLoader,
|
getCustomizableJSLoader,
|
||||||
applyConfigureWebpack,
|
applyConfigureWebpack,
|
||||||
applyConfigurePostCss,
|
applyConfigurePostCss,
|
||||||
|
getHttpsConfig,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import type {
|
import type {
|
||||||
ConfigureWebpackFn,
|
ConfigureWebpackFn,
|
||||||
|
@ -297,3 +298,65 @@ describe('extending PostCSS', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getHttpsConfig', () => {
|
||||||
|
const originalEnv = process.env;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetModules();
|
||||||
|
process.env = {...originalEnv};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
process.env = originalEnv;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true for HTTPS not env', async () => {
|
||||||
|
await expect(getHttpsConfig()).resolves.toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true for HTTPS in env', async () => {
|
||||||
|
process.env.HTTPS = 'true';
|
||||||
|
await expect(getHttpsConfig()).resolves.toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns custom certs if they are in env', async () => {
|
||||||
|
process.env.HTTPS = 'true';
|
||||||
|
process.env.SSL_CRT_FILE = path.join(__dirname, '__fixtures__/host.crt');
|
||||||
|
process.env.SSL_KEY_FILE = path.join(__dirname, '__fixtures__/host.key');
|
||||||
|
await expect(getHttpsConfig()).resolves.toEqual({
|
||||||
|
key: expect.any(Buffer),
|
||||||
|
cert: expect.any(Buffer),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws if file doesn't exist", async () => {
|
||||||
|
process.env.HTTPS = 'true';
|
||||||
|
process.env.SSL_CRT_FILE = path.join(
|
||||||
|
__dirname,
|
||||||
|
'__fixtures__/nonexistent.crt',
|
||||||
|
);
|
||||||
|
process.env.SSL_KEY_FILE = path.join(__dirname, '__fixtures__/host.key');
|
||||||
|
await expect(getHttpsConfig()).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"You specified SSL_CRT_FILE in your env, but the file \\"<PROJECT_ROOT>/packages/docusaurus/src/webpack/__tests__/__fixtures__/nonexistent.crt\\" can't be found."`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws for invalid key', async () => {
|
||||||
|
process.env.HTTPS = 'true';
|
||||||
|
process.env.SSL_CRT_FILE = path.join(__dirname, '__fixtures__/host.crt');
|
||||||
|
process.env.SSL_KEY_FILE = path.join(__dirname, '__fixtures__/invalid.key');
|
||||||
|
await expect(getHttpsConfig()).rejects.toThrowError(
|
||||||
|
/The certificate key .*[/\\]__fixtures__[/\\]invalid\.key is invalid/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws for invalid cert', async () => {
|
||||||
|
process.env.HTTPS = 'true';
|
||||||
|
process.env.SSL_CRT_FILE = path.join(__dirname, '__fixtures__/invalid.crt');
|
||||||
|
process.env.SSL_KEY_FILE = path.join(__dirname, '__fixtures__/host.key');
|
||||||
|
await expect(getHttpsConfig()).rejects.toThrowError(
|
||||||
|
/The certificate .*[/\\]__fixtures__[/\\]invalid\.crt is invalid/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -5,11 +5,6 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Inspired by https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_difference
|
|
||||||
export function difference<T>(...arrays: T[][]): T[] {
|
|
||||||
return arrays.reduce((a, b) => a.filter((c) => !b.includes(c)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inspired by https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_sortby-and-_orderby
|
// Inspired by https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_sortby-and-_orderby
|
||||||
export function sortBy<T>(
|
export function sortBy<T>(
|
||||||
array: T[],
|
array: T[],
|
||||||
|
|
Loading…
Add table
Reference in a new issue