feat(content-docs): sidebar category linking to document or auto-generated index page (#5830)

Co-authored-by: Joshua Chen <sidachen2003@gmail.com>
Co-authored-by: Armano <armano2@users.noreply.github.com>
Co-authored-by: Alexey Pyltsyn <lex61rus@gmail.com>
This commit is contained in:
Sébastien Lorber 2021-12-03 14:44:59 +01:00 committed by GitHub
parent 95f911efef
commit cfae5d0933
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
105 changed files with 3904 additions and 816 deletions

View file

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`loadUnprocessedSidebars sidebars link 1`] = `
exports[`loadNormalizedSidebars sidebars link 1`] = `
Object {
"docs": Array [
Object {
@ -14,13 +14,14 @@ Object {
},
],
"label": "Test",
"link": undefined,
"type": "category",
},
],
}
`;
exports[`loadUnprocessedSidebars sidebars with category.collapsed property 1`] = `
exports[`loadNormalizedSidebars sidebars with category.collapsed property 1`] = `
Object {
"docs": Array [
Object {
@ -37,10 +38,12 @@ Object {
},
],
"label": "Introduction",
"link": undefined,
"type": "category",
},
],
"label": "Test",
"link": undefined,
"type": "category",
},
Object {
@ -57,17 +60,19 @@ Object {
},
],
"label": "Powering MDX",
"link": undefined,
"type": "category",
},
],
"label": "Reference",
"link": undefined,
"type": "category",
},
],
}
`;
exports[`loadUnprocessedSidebars sidebars with category.collapsed property at first level 1`] = `
exports[`loadNormalizedSidebars sidebars with category.collapsed property at first level 1`] = `
Object {
"docs": Array [
Object {
@ -80,6 +85,7 @@ Object {
},
],
"label": "Introduction",
"link": undefined,
"type": "category",
},
Object {
@ -92,13 +98,14 @@ Object {
},
],
"label": "Powering MDX",
"link": undefined,
"type": "category",
},
],
}
`;
exports[`loadUnprocessedSidebars sidebars with deep level of category 1`] = `
exports[`loadNormalizedSidebars sidebars with deep level of category 1`] = `
Object {
"docs": Array [
Object {
@ -139,14 +146,17 @@ Object {
},
],
"label": "deeper more more",
"link": undefined,
"type": "category",
},
],
"label": "level 4",
"link": undefined,
"type": "category",
},
],
"label": "level 3",
"link": undefined,
"type": "category",
},
Object {
@ -155,17 +165,19 @@ Object {
},
],
"label": "level 2",
"link": undefined,
"type": "category",
},
],
"label": "level 1",
"link": undefined,
"type": "category",
},
],
}
`;
exports[`loadUnprocessedSidebars sidebars with first level not a category 1`] = `
exports[`loadNormalizedSidebars sidebars with first level not a category 1`] = `
Object {
"docs": Array [
Object {
@ -178,6 +190,7 @@ Object {
},
],
"label": "Getting Started",
"link": undefined,
"type": "category",
},
Object {
@ -188,7 +201,7 @@ Object {
}
`;
exports[`loadUnprocessedSidebars sidebars with known sidebar item type 1`] = `
exports[`loadNormalizedSidebars sidebars with known sidebar item type 1`] = `
Object {
"docs": Array [
Object {
@ -214,6 +227,7 @@ Object {
},
],
"label": "Test",
"link": undefined,
"type": "category",
},
Object {
@ -226,6 +240,7 @@ Object {
},
],
"label": "Guides",
"link": undefined,
"type": "category",
},
],

View file

@ -129,9 +129,15 @@ describe('DefaultSidebarItemsGenerator', () => {
test('generates complex nested sidebar', async () => {
mockCategoryMetadataFiles({
'02-Guides/_category_.json': {collapsed: false},
'02-Guides/_category_.json': {collapsed: false} as CategoryMetadataFile,
'02-Guides/01-SubGuides/_category_.yml': {
label: 'SubGuides (metadata file label)',
link: {
type: 'generated-index',
slug: 'subguides-generated-index-slug',
title: 'subguides-title',
description: 'subguides-description',
},
},
});
@ -153,6 +159,13 @@ describe('DefaultSidebarItemsGenerator', () => {
sidebarPosition: 1,
frontMatter: {},
},
{
id: 'tutorials-index',
source: 'index.md',
sourceDirName: '01-Tutorials',
sidebarPosition: 2,
frontMatter: {},
},
{
id: 'tutorial2',
source: 'tutorial2.md',
@ -167,6 +180,12 @@ describe('DefaultSidebarItemsGenerator', () => {
sidebarPosition: 1,
frontMatter: {},
},
{
id: 'guides-index',
source: '02-Guides.md', // TODO should we allow to just use "Guides.md" to have an index?
sourceDirName: '02-Guides',
frontMatter: {},
},
{
id: 'guide2',
source: 'guide2.md',
@ -209,6 +228,10 @@ describe('DefaultSidebarItemsGenerator', () => {
label: 'Tutorials',
collapsed: true,
collapsible: true,
link: {
type: 'doc',
id: 'tutorials-index',
},
items: [
{type: 'doc', id: 'tutorial1'},
{type: 'doc', id: 'tutorial2'},
@ -219,6 +242,10 @@ describe('DefaultSidebarItemsGenerator', () => {
label: 'Guides',
collapsed: false,
collapsible: true,
link: {
type: 'doc',
id: 'guides-index',
},
items: [
{type: 'doc', id: 'guide1'},
{
@ -227,6 +254,12 @@ describe('DefaultSidebarItemsGenerator', () => {
collapsed: true,
collapsible: true,
items: [{type: 'doc', id: 'nested-guide'}],
link: {
type: 'generated-index',
slug: 'subguides-generated-index-slug',
title: 'subguides-title',
description: 'subguides-description',
},
},
{type: 'doc', id: 'guide2'},
],
@ -354,4 +387,75 @@ describe('DefaultSidebarItemsGenerator', () => {
},
] as Sidebar);
});
test('uses explicit link over the index/readme.{md,mdx} naming convention', async () => {
mockCategoryMetadataFiles({
'Category/_category_.yml': {
label: 'Category label',
link: {
type: 'doc',
id: 'doc3', // Using a "local doc id" ("doc1" instead of "parent/doc1") on purpose
},
},
});
const sidebarSlice = await DefaultSidebarItemsGenerator({
numberPrefixParser: DefaultNumberPrefixParser,
item: {
type: 'autogenerated',
dirName: '.',
},
version: {
versionName: 'current',
contentPath: '',
},
docs: [
{
id: 'parent/doc1',
source: 'index.md',
sourceDirName: 'Category',
frontMatter: {},
},
{
id: 'parent/doc2',
source: 'index.md',
sourceDirName: 'Category',
frontMatter: {},
},
{
id: 'parent/doc3',
source: 'doc3.md',
sourceDirName: 'Category',
frontMatter: {},
},
],
options: {
sidebarCollapsed: true,
sidebarCollapsible: true,
},
});
expect(sidebarSlice).toEqual([
{
type: 'category',
label: 'Category label',
collapsed: true,
collapsible: true,
link: {
id: 'parent/doc3',
type: 'doc',
},
items: [
{
id: 'parent/doc1',
type: 'doc',
},
{
id: 'parent/doc2',
type: 'doc',
},
],
},
] as Sidebar);
});
});

View file

@ -7,27 +7,31 @@
import path from 'path';
import {
loadUnprocessedSidebars,
loadNormalizedSidebars,
DefaultSidebars,
DisabledSidebars,
} from '../index';
import type {SidebarOptions} from '../../types';
import type {NormalizeSidebarsParams, VersionMetadata} from '../../types';
describe('loadUnprocessedSidebars', () => {
describe('loadNormalizedSidebars', () => {
const fixtureDir = path.join(__dirname, '__fixtures__', 'sidebars');
const options: SidebarOptions = {
const options: NormalizeSidebarsParams = {
sidebarCollapsed: true,
sidebarCollapsible: true,
version: {
versionName: 'version',
versionPath: 'versionPath',
} as VersionMetadata,
};
test('sidebars with known sidebar item type', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars.json');
const result = loadUnprocessedSidebars(sidebarPath, options);
const result = loadNormalizedSidebars(sidebarPath, options);
expect(result).toMatchSnapshot();
});
test('sidebars with deep level of category', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-category.js');
const result = loadUnprocessedSidebars(sidebarPath, options);
const result = loadNormalizedSidebars(sidebarPath, options);
expect(result).toMatchSnapshot();
});
@ -37,8 +41,8 @@ describe('loadUnprocessedSidebars', () => {
fixtureDir,
'sidebars-category-shorthand.js',
);
const sidebar1 = loadUnprocessedSidebars(sidebarPath1, options);
const sidebar2 = loadUnprocessedSidebars(sidebarPath2, options);
const sidebar1 = loadNormalizedSidebars(sidebarPath1, options);
const sidebar2 = loadNormalizedSidebars(sidebarPath2, options);
expect(sidebar1).toEqual(sidebar2);
});
@ -47,7 +51,7 @@ describe('loadUnprocessedSidebars', () => {
fixtureDir,
'sidebars-category-wrong-items.json',
);
expect(() => loadUnprocessedSidebars(sidebarPath, options))
expect(() => loadNormalizedSidebars(sidebarPath, options))
.toThrowErrorMatchingInlineSnapshot(`
"{
\\"type\\": \\"category\\",
@ -64,7 +68,7 @@ describe('loadUnprocessedSidebars', () => {
fixtureDir,
'sidebars-category-wrong-label.json',
);
expect(() => loadUnprocessedSidebars(sidebarPath, options))
expect(() => loadNormalizedSidebars(sidebarPath, options))
.toThrowErrorMatchingInlineSnapshot(`
"{
\\"type\\": \\"category\\",
@ -83,7 +87,7 @@ describe('loadUnprocessedSidebars', () => {
fixtureDir,
'sidebars-doc-id-not-string.json',
);
expect(() => loadUnprocessedSidebars(sidebarPath, options))
expect(() => loadNormalizedSidebars(sidebarPath, options))
.toThrowErrorMatchingInlineSnapshot(`
"{
\\"type\\": \\"doc\\",
@ -101,19 +105,19 @@ describe('loadUnprocessedSidebars', () => {
fixtureDir,
'sidebars-first-level-not-category.js',
);
const result = loadUnprocessedSidebars(sidebarPath, options);
const result = loadNormalizedSidebars(sidebarPath, options);
expect(result).toMatchSnapshot();
});
test('sidebars link', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-link.json');
const result = loadUnprocessedSidebars(sidebarPath, options);
const result = loadNormalizedSidebars(sidebarPath, options);
expect(result).toMatchSnapshot();
});
test('sidebars link wrong label', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-link-wrong-label.json');
expect(() => loadUnprocessedSidebars(sidebarPath, options))
expect(() => loadNormalizedSidebars(sidebarPath, options))
.toThrowErrorMatchingInlineSnapshot(`
"{
\\"type\\": \\"link\\",
@ -127,7 +131,7 @@ describe('loadUnprocessedSidebars', () => {
test('sidebars link wrong href', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-link-wrong-href.json');
expect(() => loadUnprocessedSidebars(sidebarPath, options))
expect(() => loadNormalizedSidebars(sidebarPath, options))
.toThrowErrorMatchingInlineSnapshot(`
"{
\\"type\\": \\"link\\",
@ -143,7 +147,7 @@ describe('loadUnprocessedSidebars', () => {
test('sidebars with unknown sidebar item type', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-unknown-type.json');
expect(() => loadUnprocessedSidebars(sidebarPath, options))
expect(() => loadNormalizedSidebars(sidebarPath, options))
.toThrowErrorMatchingInlineSnapshot(`
"{
\\"type\\": \\"superman\\",
@ -156,7 +160,7 @@ describe('loadUnprocessedSidebars', () => {
test('sidebars with known sidebar item type but wrong field', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-wrong-field.json');
expect(() => loadUnprocessedSidebars(sidebarPath, options))
expect(() => loadNormalizedSidebars(sidebarPath, options))
.toThrowErrorMatchingInlineSnapshot(`
"{
\\"type\\": \\"category\\",
@ -170,24 +174,22 @@ describe('loadUnprocessedSidebars', () => {
});
test('unexisting path', () => {
expect(loadUnprocessedSidebars('badpath', options)).toEqual(
expect(loadNormalizedSidebars('badpath', options)).toEqual(
DisabledSidebars,
);
});
test('undefined path', () => {
expect(loadUnprocessedSidebars(undefined, options)).toEqual(
DefaultSidebars,
);
expect(loadNormalizedSidebars(undefined, options)).toEqual(DefaultSidebars);
});
test('literal false path', () => {
expect(loadUnprocessedSidebars(false, options)).toEqual(DisabledSidebars);
expect(loadNormalizedSidebars(false, options)).toEqual(DisabledSidebars);
});
test('sidebars with category.collapsed property', async () => {
const sidebarPath = path.join(fixtureDir, 'sidebars-collapsed.json');
const result = loadUnprocessedSidebars(sidebarPath, options);
const result = loadNormalizedSidebars(sidebarPath, options);
expect(result).toMatchSnapshot();
});
@ -196,7 +198,7 @@ describe('loadUnprocessedSidebars', () => {
fixtureDir,
'sidebars-collapsed-first-level.json',
);
const result = loadUnprocessedSidebars(sidebarPath, options);
const result = loadNormalizedSidebars(sidebarPath, options);
expect(result).toMatchSnapshot();
});
});

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {processSidebars} from '../processor';
import {processSidebars, SidebarProcessorParams} from '../processor';
import type {
SidebarItem,
SidebarItemsGenerator,
@ -13,23 +13,50 @@ import type {
NormalizedSidebars,
} from '../types';
import {DefaultSidebarItemsGenerator} from '../generator';
import {createSlugger} from '@docusaurus/utils';
import {VersionMetadata} from '../../types';
import {DefaultNumberPrefixParser} from '../../numberPrefix';
describe('processSidebars', () => {
function createStaticSidebarItemGenerator(
sidebarSlice: SidebarItem[],
): SidebarItemsGenerator {
return jest.fn(async () => sidebarSlice);
}
const StaticGeneratedSidebarSlice: SidebarItem[] = [
{type: 'doc', id: 'doc-generated-id-1'},
{type: 'doc', id: 'doc-generated-id-2'},
];
const StaticSidebarItemsGenerator: SidebarItemsGenerator = jest.fn(
async () => StaticGeneratedSidebarSlice,
);
const StaticSidebarItemsGenerator: SidebarItemsGenerator =
createStaticSidebarItemGenerator(StaticGeneratedSidebarSlice);
async function testProcessSidebars(unprocessedSidebars: NormalizedSidebars) {
// @ts-expect-error: good enough for this test
const version: VersionMetadata = {
versionName: '1.0.0',
versionPath: '/docs/1.0.0',
};
const params: SidebarProcessorParams = {
sidebarItemsGenerator: StaticSidebarItemsGenerator,
docs: [],
version,
numberPrefixParser: DefaultNumberPrefixParser,
categoryLabelSlugger: createSlugger(),
sidebarOptions: {
sidebarCollapsed: true,
sidebarCollapsible: true,
},
};
async function testProcessSidebars(
unprocessedSidebars: NormalizedSidebars,
paramsOverrides: Partial<SidebarProcessorParams> = {},
) {
return processSidebars(unprocessedSidebars, {
sidebarItemsGenerator: StaticSidebarItemsGenerator,
docs: [],
// @ts-expect-error: useless for this test
version: {},
...params,
...paramsOverrides,
});
}
@ -69,13 +96,18 @@ describe('processSidebars', () => {
{type: 'doc', id: 'doc1'},
{
type: 'category',
label: 'Category',
link: {
type: 'generated-index',
slug: 'category-generated-index-slug',
permalink: 'category-generated-index-permalink',
},
collapsed: false,
collapsible: true,
items: [
{type: 'doc', id: 'doc2'},
{type: 'autogenerated', dirName: 'dir1'},
],
label: 'Category',
},
{type: 'link', href: 'https://facebook.com', label: 'FB'},
],
@ -86,10 +118,10 @@ describe('processSidebars', () => {
{type: 'autogenerated', dirName: 'dir3'},
{
type: 'category',
label: 'Category',
collapsed: false,
collapsible: true,
items: [{type: 'doc', id: 'doc4'}],
label: 'Category',
},
],
};
@ -100,20 +132,32 @@ describe('processSidebars', () => {
expect(StaticSidebarItemsGenerator).toHaveBeenCalledWith({
defaultSidebarItemsGenerator: DefaultSidebarItemsGenerator,
item: {type: 'autogenerated', dirName: 'dir1'},
docs: [],
version: {},
docs: params.docs,
version: {
versionName: version.versionName,
},
numberPrefixParser: DefaultNumberPrefixParser,
options: params.sidebarOptions,
});
expect(StaticSidebarItemsGenerator).toHaveBeenCalledWith({
defaultSidebarItemsGenerator: DefaultSidebarItemsGenerator,
item: {type: 'autogenerated', dirName: 'dir2'},
docs: [],
version: {},
docs: params.docs,
version: {
versionName: version.versionName,
},
numberPrefixParser: DefaultNumberPrefixParser,
options: params.sidebarOptions,
});
expect(StaticSidebarItemsGenerator).toHaveBeenCalledWith({
defaultSidebarItemsGenerator: DefaultSidebarItemsGenerator,
item: {type: 'autogenerated', dirName: 'dir3'},
docs: [],
version: {},
docs: params.docs,
version: {
versionName: version.versionName,
},
numberPrefixParser: DefaultNumberPrefixParser,
options: params.sidebarOptions,
});
expect(processedSidebar).toEqual({
@ -121,10 +165,15 @@ describe('processSidebars', () => {
{type: 'doc', id: 'doc1'},
{
type: 'category',
label: 'Category',
link: {
type: 'generated-index',
slug: 'category-generated-index-slug',
permalink: 'category-generated-index-permalink',
},
collapsed: false,
collapsible: true,
items: [{type: 'doc', id: 'doc2'}, ...StaticGeneratedSidebarSlice],
label: 'Category',
},
{type: 'link', href: 'https://facebook.com', label: 'FB'},
],
@ -135,10 +184,52 @@ describe('processSidebars', () => {
...StaticGeneratedSidebarSlice,
{
type: 'category',
label: 'Category',
collapsed: false,
collapsible: true,
items: [{type: 'doc', id: 'doc4'}],
label: 'Category',
},
],
} as Sidebars);
});
test('ensure generated items are normalized', async () => {
const sidebarSliceContainingCategoryGeneratedIndex: SidebarItem[] = [
{
type: 'category',
label: 'Generated category',
link: {
type: 'generated-index',
slug: 'generated-cat-index-slug',
// @ts-expect-error: TODO undefined should be allowed here, typing error needing refactor
permalink: undefined,
},
},
];
const unprocessedSidebars: NormalizedSidebars = {
someSidebar: [{type: 'autogenerated', dirName: 'dir2'}],
};
const processedSidebar = await testProcessSidebars(unprocessedSidebars, {
sidebarItemsGenerator: createStaticSidebarItemGenerator(
sidebarSliceContainingCategoryGeneratedIndex,
),
});
expect(processedSidebar).toEqual({
someSidebar: [
{
type: 'category',
label: 'Generated category',
link: {
type: 'generated-index',
slug: 'generated-cat-index-slug',
permalink: '/docs/1.0.0/generated-cat-index-slug',
},
items: [],
collapsible: true,
collapsed: true,
},
],
} as Sidebars);

View file

@ -12,8 +12,12 @@ import {
collectSidebarLinks,
transformSidebarItems,
collectSidebarsDocIds,
SidebarNavigation,
toDocNavigationLink,
toNavigationLink,
} from '../utils';
import type {Sidebar, Sidebars} from '../types';
import {DocMetadataBase, DocNavLink} from '../../types';
describe('createSidebarsUtils', () => {
const sidebar1: Sidebar = [
@ -21,13 +25,13 @@ describe('createSidebarsUtils', () => {
type: 'category',
collapsed: false,
collapsible: true,
label: 'Category1',
label: 'S1 Category',
items: [
{
type: 'category',
collapsed: false,
collapsible: true,
label: 'Subcategory 1',
label: 'S1 Subcategory',
items: [{type: 'doc', id: 'doc1'}],
},
{type: 'doc', id: 'doc2'},
@ -40,7 +44,7 @@ describe('createSidebarsUtils', () => {
type: 'category',
collapsed: false,
collapsible: true,
label: 'Category2',
label: 'S2 Category',
items: [
{type: 'doc', id: 'doc3'},
{type: 'doc', id: 'doc4'},
@ -48,10 +52,58 @@ describe('createSidebarsUtils', () => {
},
];
const sidebars: Sidebars = {sidebar1, sidebar2};
const sidebar3: Sidebar = [
{
type: 'category',
collapsed: false,
collapsible: true,
label: 'S3 Category',
link: {
type: 'doc',
id: 'doc5',
},
items: [
{
type: 'category',
collapsed: false,
collapsible: true,
label: 'S3 SubCategory',
link: {
type: 'generated-index',
slug: '/s3-subcategory-index-slug',
permalink: '/s3-subcategory-index-permalink',
},
items: [
{
type: 'category',
collapsed: false,
collapsible: true,
label: 'S3 SubSubCategory',
link: {
type: 'generated-index',
slug: '/s3-subsubcategory-slug',
permalink: '/s3-subsubcategory-index-permalink',
},
items: [
{type: 'doc', id: 'doc6'},
{type: 'doc', id: 'doc7'},
],
},
],
},
],
},
];
const {getFirstDocIdOfFirstSidebar, getSidebarNameByDocId, getDocNavigation} =
createSidebarsUtils(sidebars);
const sidebars: Sidebars = {sidebar1, sidebar2, sidebar3};
const {
getFirstDocIdOfFirstSidebar,
getSidebarNameByDocId,
getDocNavigation,
getCategoryGeneratedIndexNavigation,
getCategoryGeneratedIndexList,
} = createSidebarsUtils(sidebars);
test('getSidebarNameByDocId', async () => {
expect(getFirstDocIdOfFirstSidebar()).toEqual('doc1');
@ -62,32 +114,117 @@ describe('createSidebarsUtils', () => {
expect(getSidebarNameByDocId('doc2')).toEqual('sidebar1');
expect(getSidebarNameByDocId('doc3')).toEqual('sidebar2');
expect(getSidebarNameByDocId('doc4')).toEqual('sidebar2');
expect(getSidebarNameByDocId('doc5')).toEqual(undefined);
expect(getSidebarNameByDocId('doc6')).toEqual(undefined);
expect(getSidebarNameByDocId('doc5')).toEqual('sidebar3');
expect(getSidebarNameByDocId('doc6')).toEqual('sidebar3');
expect(getSidebarNameByDocId('doc7')).toEqual('sidebar3');
expect(getSidebarNameByDocId('unknown_id')).toEqual(undefined);
});
test('getDocNavigation', async () => {
expect(getDocNavigation('doc1')).toEqual({
sidebarName: 'sidebar1',
previousId: undefined,
nextId: 'doc2',
});
previous: undefined,
next: {
type: 'doc',
id: 'doc2',
},
} as SidebarNavigation);
expect(getDocNavigation('doc2')).toEqual({
sidebarName: 'sidebar1',
previousId: 'doc1',
nextId: undefined,
});
previous: {
type: 'doc',
id: 'doc1',
},
next: undefined,
} as SidebarNavigation);
expect(getDocNavigation('doc3')).toEqual({
sidebarName: 'sidebar2',
previousId: undefined,
nextId: 'doc4',
});
previous: undefined,
next: {
type: 'doc',
id: 'doc4',
},
} as SidebarNavigation);
expect(getDocNavigation('doc4')).toEqual({
sidebarName: 'sidebar2',
previousId: 'doc3',
nextId: undefined,
});
previous: {
type: 'doc',
id: 'doc3',
},
next: undefined,
} as SidebarNavigation);
expect(getDocNavigation('doc5')).toMatchObject({
sidebarName: 'sidebar3',
previous: undefined,
next: {
type: 'category',
label: 'S3 SubCategory',
},
} as SidebarNavigation);
expect(getDocNavigation('doc6')).toMatchObject({
sidebarName: 'sidebar3',
previous: {
type: 'category',
label: 'S3 SubSubCategory',
},
next: {
type: 'doc',
id: 'doc7',
},
} as SidebarNavigation);
expect(getDocNavigation('doc7')).toMatchObject({
sidebarName: 'sidebar3',
previous: {
type: 'doc',
id: 'doc6',
},
next: undefined,
} as SidebarNavigation);
});
test('getCategoryGeneratedIndexNavigation', async () => {
expect(
getCategoryGeneratedIndexNavigation('/s3-subcategory-index-permalink'),
).toMatchObject({
sidebarName: 'sidebar3',
previous: {
type: 'category',
label: 'S3 Category',
},
next: {
type: 'category',
label: 'S3 SubSubCategory',
},
} as SidebarNavigation);
expect(
getCategoryGeneratedIndexNavigation('/s3-subsubcategory-index-permalink'),
).toMatchObject({
sidebarName: 'sidebar3',
previous: {
type: 'category',
label: 'S3 SubCategory',
},
next: {
type: 'doc',
id: 'doc6',
},
} as SidebarNavigation);
});
test('getCategoryGeneratedIndexList', async () => {
expect(getCategoryGeneratedIndexList()).toMatchObject([
{
type: 'category',
label: 'S3 SubCategory',
},
{
type: 'category',
label: 'S3 SubSubCategory',
},
]);
});
});
@ -393,3 +530,166 @@ describe('transformSidebarItems', () => {
]);
});
});
describe('toDocNavigationLink', () => {
type TestDoc = Pick<DocMetadataBase, 'permalink' | 'title' | 'frontMatter'>;
function testDoc(data: TestDoc) {
return data as DocMetadataBase;
}
test('with no frontmatter', () => {
expect(
toDocNavigationLink(
testDoc({
title: 'Doc Title',
permalink: '/docPermalink',
frontMatter: {},
}),
),
).toEqual({
title: 'Doc Title',
permalink: '/docPermalink',
} as DocNavLink);
});
test('with pagination_label frontmatter', () => {
expect(
toDocNavigationLink(
testDoc({
title: 'Doc Title',
permalink: '/docPermalink',
frontMatter: {
pagination_label: 'pagination_label',
},
}),
),
).toEqual({
title: 'pagination_label',
permalink: '/docPermalink',
} as DocNavLink);
});
test('with sidebar_label frontmatter', () => {
expect(
toDocNavigationLink(
testDoc({
title: 'Doc Title',
permalink: '/docPermalink',
frontMatter: {
sidebar_label: 'sidebar_label',
},
}),
),
).toEqual({
title: 'sidebar_label',
permalink: '/docPermalink',
} as DocNavLink);
});
test('with pagination_label + sidebar_label frontmatter', () => {
expect(
toDocNavigationLink(
testDoc({
title: 'Doc Title',
permalink: '/docPermalink',
frontMatter: {
pagination_label: 'pagination_label',
sidebar_label: 'sidebar_label',
},
}),
),
).toEqual({
title: 'pagination_label',
permalink: '/docPermalink',
} as DocNavLink);
});
});
describe('toNavigationLink', () => {
type TestDoc = Pick<DocMetadataBase, 'permalink' | 'title'>;
function testDoc(data: TestDoc) {
return {...data, frontMatter: {}} as DocMetadataBase;
}
const docsById: Record<string, DocMetadataBase> = {
doc1: testDoc({
title: 'Doc 1',
permalink: '/doc1',
}),
doc2: testDoc({
title: 'Doc 1',
permalink: '/doc1',
}),
};
test('with doc items', () => {
expect(toNavigationLink({type: 'doc', id: 'doc1'}, docsById)).toEqual(
toDocNavigationLink(docsById.doc1),
);
expect(toNavigationLink({type: 'doc', id: 'doc2'}, docsById)).toEqual(
toDocNavigationLink(docsById.doc2),
);
expect(() =>
toNavigationLink({type: 'doc', id: 'doc3'}, docsById),
).toThrowErrorMatchingInlineSnapshot(
`"Can't create navigation link: no doc found with id=doc3"`,
);
});
test('with category item and doc link', () => {
expect(
toNavigationLink(
{
type: 'category',
label: 'Category',
items: [],
link: {
type: 'doc',
id: 'doc1',
},
collapsed: true,
collapsible: true,
},
docsById,
),
).toEqual(toDocNavigationLink(docsById.doc1));
expect(() =>
toNavigationLink(
{
type: 'category',
label: 'Category',
items: [],
link: {
type: 'doc',
id: 'doc3',
},
collapsed: true,
collapsible: true,
},
docsById,
),
).toThrowErrorMatchingInlineSnapshot(
`"Can't create navigation link: no doc found with id=doc3"`,
);
});
test('with category item and generated-index link', () => {
expect(
toNavigationLink(
{
type: 'category',
label: 'Category',
items: [],
link: {
type: 'generated-index',
slug: 'slug',
permalink: 'generated-index-permalink',
},
collapsed: true,
collapsible: true,
},
docsById,
),
).toEqual({title: 'Category', permalink: 'generated-index-permalink'});
});
});

View file

@ -0,0 +1,105 @@
/**
* 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 {validateSidebars, validateCategoryMetadataFile} from '../validation';
import {CategoryMetadataFile} from '../generator';
import {SidebarsConfig} from '../types';
describe('validateSidebars', () => {
// TODO add more tests
// TODO it seems many error cases are not validated properly
// and error messages are quite bad
test('throw for bad value', async () => {
expect(() => validateSidebars({sidebar: [{type: 42}]}))
.toThrowErrorMatchingInlineSnapshot(`
"{
\\"type\\": 42,
\\"undefined\\" [1]: -- missing --
}

[1] Unknown sidebar item type \\"42\\"."
`);
});
test('accept empty object', async () => {
const sidebars: SidebarsConfig = {};
validateSidebars(sidebars);
});
test('accept valid values', async () => {
const sidebars: SidebarsConfig = {
sidebar1: [
{type: 'doc', id: 'doc1'},
{type: 'doc', id: 'doc2'},
{
type: 'category',
label: 'Category',
items: [{type: 'doc', id: 'doc3'}],
},
],
};
validateSidebars(sidebars);
});
});
describe('validateCategoryMetadataFile', () => {
// TODO add more tests
test('throw for bad value', async () => {
expect(() =>
validateCategoryMetadataFile(42),
).toThrowErrorMatchingInlineSnapshot(
`"\\"value\\" must be of type object"`,
);
});
test('accept empty object', async () => {
const content: CategoryMetadataFile = {};
expect(validateCategoryMetadataFile(content)).toEqual(content);
});
test('accept valid values', async () => {
const content: CategoryMetadataFile = {
className: 'className',
label: 'Category Label',
link: {
type: 'generated-index',
slug: 'slug',
title: 'title',
description: 'description',
},
collapsible: true,
collapsed: true,
position: 3,
};
expect(validateCategoryMetadataFile(content)).toEqual(content);
});
test('rejects permalink', async () => {
const content: CategoryMetadataFile = {
className: 'className',
label: 'Category Label',
link: {
type: 'generated-index',
slug: 'slug',
// @ts-expect-error: rejected on purpose
permalink: 'somePermalink',
title: 'title',
description: 'description',
},
collapsible: true,
collapsed: true,
position: 3,
};
expect(() =>
validateCategoryMetadataFile(content),
).toThrowErrorMatchingInlineSnapshot(
`"\\"link.permalink\\" is not allowed"`,
);
});
});