mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-11 07:12:29 +02:00
test(theme-common): improve test coverage (#6902)
* test(theme-common): improve test coverage * revert
This commit is contained in:
parent
aa5a2d4c04
commit
f6baaa6b75
59 changed files with 1183 additions and 755 deletions
|
@ -0,0 +1,40 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`useTreeifiedTOC treeifies TOC without filtering 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [
|
||||
Object {
|
||||
"children": Array [],
|
||||
"id": "foxtrot",
|
||||
"level": 6,
|
||||
"value": "Foxtrot",
|
||||
},
|
||||
],
|
||||
"id": "echo",
|
||||
"level": 5,
|
||||
"value": "Echo",
|
||||
},
|
||||
],
|
||||
"id": "delta",
|
||||
"level": 4,
|
||||
"value": "Delta",
|
||||
},
|
||||
],
|
||||
"id": "charlie",
|
||||
"level": 3,
|
||||
"value": "Charlie",
|
||||
},
|
||||
],
|
||||
"id": "bravo",
|
||||
"level": 2,
|
||||
"value": "Bravo",
|
||||
},
|
||||
]
|
||||
`;
|
|
@ -13,45 +13,45 @@ import {
|
|||
|
||||
describe('parseCodeBlockTitle', () => {
|
||||
it('parses double quote delimited title', () => {
|
||||
expect(parseCodeBlockTitle(`title="index.js"`)).toEqual(`index.js`);
|
||||
expect(parseCodeBlockTitle(`title="index.js"`)).toBe(`index.js`);
|
||||
});
|
||||
|
||||
it('parses single quote delimited title', () => {
|
||||
expect(parseCodeBlockTitle(`title='index.js'`)).toEqual(`index.js`);
|
||||
expect(parseCodeBlockTitle(`title='index.js'`)).toBe(`index.js`);
|
||||
});
|
||||
|
||||
it('does not parse mismatched quote delimiters', () => {
|
||||
expect(parseCodeBlockTitle(`title="index.js'`)).toEqual(``);
|
||||
expect(parseCodeBlockTitle(`title="index.js'`)).toBe(``);
|
||||
});
|
||||
|
||||
it('parses undefined metastring', () => {
|
||||
expect(parseCodeBlockTitle(undefined)).toEqual(``);
|
||||
expect(parseCodeBlockTitle(undefined)).toBe(``);
|
||||
});
|
||||
|
||||
it('parses metastring with no title specified', () => {
|
||||
expect(parseCodeBlockTitle(`{1,2-3}`)).toEqual(``);
|
||||
expect(parseCodeBlockTitle(`{1,2-3}`)).toBe(``);
|
||||
});
|
||||
|
||||
it('parses with multiple metadata title first', () => {
|
||||
expect(parseCodeBlockTitle(`title="index.js" label="JavaScript"`)).toEqual(
|
||||
expect(parseCodeBlockTitle(`title="index.js" label="JavaScript"`)).toBe(
|
||||
`index.js`,
|
||||
);
|
||||
});
|
||||
|
||||
it('parses with multiple metadata title last', () => {
|
||||
expect(parseCodeBlockTitle(`label="JavaScript" title="index.js"`)).toEqual(
|
||||
expect(parseCodeBlockTitle(`label="JavaScript" title="index.js"`)).toBe(
|
||||
`index.js`,
|
||||
);
|
||||
});
|
||||
|
||||
it('parses double quotes when delimited by single quotes', () => {
|
||||
expect(parseCodeBlockTitle(`title='console.log("Hello, World!")'`)).toEqual(
|
||||
expect(parseCodeBlockTitle(`title='console.log("Hello, World!")'`)).toBe(
|
||||
`console.log("Hello, World!")`,
|
||||
);
|
||||
});
|
||||
|
||||
it('parses single quotes when delimited by double quotes', () => {
|
||||
expect(parseCodeBlockTitle(`title="console.log('Hello, World!')"`)).toEqual(
|
||||
expect(parseCodeBlockTitle(`title="console.log('Hello, World!')"`)).toBe(
|
||||
`console.log('Hello, World!')`,
|
||||
);
|
||||
});
|
||||
|
@ -59,8 +59,8 @@ describe('parseCodeBlockTitle', () => {
|
|||
|
||||
describe('parseLanguage', () => {
|
||||
it('works', () => {
|
||||
expect(parseLanguage('language-foo xxx yyy')).toEqual('foo');
|
||||
expect(parseLanguage('xxxxx language-foo yyy')).toEqual('foo');
|
||||
expect(parseLanguage('language-foo xxx yyy')).toBe('foo');
|
||||
expect(parseLanguage('xxxxx language-foo yyy')).toBe('foo');
|
||||
expect(parseLanguage('xx-language-foo yyyy')).toBeUndefined();
|
||||
expect(parseLanguage('xxx yyy zzz')).toBeUndefined();
|
||||
});
|
||||
|
|
|
@ -16,8 +16,11 @@ import {
|
|||
useDocsSidebar,
|
||||
DocsSidebarProvider,
|
||||
findSidebarCategory,
|
||||
getBreadcrumbs,
|
||||
useCurrentSidebarCategory,
|
||||
useSidebarBreadcrumbs,
|
||||
} from '../docsUtils';
|
||||
import {StaticRouter} from 'react-router-dom';
|
||||
import {Context} from '@docusaurus/docusaurusContext';
|
||||
import type {
|
||||
PropSidebar,
|
||||
PropSidebarItem,
|
||||
|
@ -123,7 +126,7 @@ describe('useDocById', () => {
|
|||
},
|
||||
});
|
||||
|
||||
function callHook(docId: string | undefined) {
|
||||
function mockUseDocById(docId: string | undefined) {
|
||||
const {result} = renderHook(() => useDocById(docId), {
|
||||
wrapper: ({children}) => (
|
||||
<DocsVersionProvider version={version}>{children}</DocsVersionProvider>
|
||||
|
@ -133,25 +136,25 @@ describe('useDocById', () => {
|
|||
}
|
||||
|
||||
it('accepts undefined', () => {
|
||||
expect(callHook(undefined)).toBeUndefined();
|
||||
expect(mockUseDocById(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('finds doc1', () => {
|
||||
expect(callHook('doc1')).toMatchObject({id: 'doc1'});
|
||||
expect(mockUseDocById('doc1')).toMatchObject({id: 'doc1'});
|
||||
});
|
||||
it('finds doc2', () => {
|
||||
expect(callHook('doc2')).toMatchObject({id: 'doc2'});
|
||||
expect(mockUseDocById('doc2')).toMatchObject({id: 'doc2'});
|
||||
});
|
||||
|
||||
it('throws for doc3', () => {
|
||||
expect(() => callHook('doc3')).toThrowErrorMatchingInlineSnapshot(
|
||||
expect(() => mockUseDocById('doc3')).toThrowErrorMatchingInlineSnapshot(
|
||||
`"no version doc found by id=doc3"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findSidebarCategory', () => {
|
||||
it('os able to return undefined', () => {
|
||||
it('is able to return undefined', () => {
|
||||
expect(findSidebarCategory([], () => false)).toBeUndefined();
|
||||
expect(
|
||||
findSidebarCategory([testCategory(), testCategory()], () => false),
|
||||
|
@ -207,7 +210,7 @@ describe('findFirstCategoryLink', () => {
|
|||
href: undefined,
|
||||
}),
|
||||
),
|
||||
).toEqual(undefined);
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('works with category with link', () => {
|
||||
|
@ -217,7 +220,7 @@ describe('findFirstCategoryLink', () => {
|
|||
href: '/itemPath',
|
||||
}),
|
||||
),
|
||||
).toEqual('/itemPath');
|
||||
).toBe('/itemPath');
|
||||
});
|
||||
|
||||
it('works with category with deeply nested category link', () => {
|
||||
|
@ -239,7 +242,7 @@ describe('findFirstCategoryLink', () => {
|
|||
],
|
||||
}),
|
||||
),
|
||||
).toEqual('/itemPath');
|
||||
).toBe('/itemPath');
|
||||
});
|
||||
|
||||
it('works with category with deeply nested link', () => {
|
||||
|
@ -255,7 +258,7 @@ describe('findFirstCategoryLink', () => {
|
|||
],
|
||||
}),
|
||||
),
|
||||
).toEqual('/itemPath');
|
||||
).toBe('/itemPath');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -267,15 +270,15 @@ describe('isActiveSidebarItem', () => {
|
|||
label: 'Label',
|
||||
};
|
||||
|
||||
expect(isActiveSidebarItem(item, '/unexistingPath')).toEqual(false);
|
||||
expect(isActiveSidebarItem(item, '/unexistingPath')).toBe(false);
|
||||
|
||||
expect(isActiveSidebarItem(item, '/itemPath')).toEqual(true);
|
||||
expect(isActiveSidebarItem(item, '/itemPath')).toBe(true);
|
||||
|
||||
// Ensure it's not trailing slash sensitive:
|
||||
expect(isActiveSidebarItem(item, '/itemPath/')).toEqual(true);
|
||||
expect(isActiveSidebarItem(item, '/itemPath/')).toBe(true);
|
||||
expect(
|
||||
isActiveSidebarItem({...item, href: '/itemPath/'}, '/itemPath'),
|
||||
).toEqual(true);
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('works with category href', () => {
|
||||
|
@ -283,15 +286,15 @@ describe('isActiveSidebarItem', () => {
|
|||
href: '/itemPath',
|
||||
});
|
||||
|
||||
expect(isActiveSidebarItem(item, '/unexistingPath')).toEqual(false);
|
||||
expect(isActiveSidebarItem(item, '/unexistingPath')).toBe(false);
|
||||
|
||||
expect(isActiveSidebarItem(item, '/itemPath')).toEqual(true);
|
||||
expect(isActiveSidebarItem(item, '/itemPath')).toBe(true);
|
||||
|
||||
// Ensure it's not trailing slash sensitive:
|
||||
expect(isActiveSidebarItem(item, '/itemPath/')).toEqual(true);
|
||||
expect(isActiveSidebarItem(item, '/itemPath/')).toBe(true);
|
||||
expect(
|
||||
isActiveSidebarItem({...item, href: '/itemPath/'}, '/itemPath'),
|
||||
).toEqual(true);
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('works with category nested items', () => {
|
||||
|
@ -316,63 +319,70 @@ describe('isActiveSidebarItem', () => {
|
|||
],
|
||||
});
|
||||
|
||||
expect(isActiveSidebarItem(item, '/unexistingPath')).toEqual(false);
|
||||
expect(isActiveSidebarItem(item, '/unexistingPath')).toBe(false);
|
||||
|
||||
expect(isActiveSidebarItem(item, '/category-path')).toEqual(true);
|
||||
expect(isActiveSidebarItem(item, '/sub-link-path')).toEqual(true);
|
||||
expect(isActiveSidebarItem(item, '/sub-category-path')).toEqual(true);
|
||||
expect(isActiveSidebarItem(item, '/sub-sub-link-path')).toEqual(true);
|
||||
expect(isActiveSidebarItem(item, '/category-path')).toBe(true);
|
||||
expect(isActiveSidebarItem(item, '/sub-link-path')).toBe(true);
|
||||
expect(isActiveSidebarItem(item, '/sub-category-path')).toBe(true);
|
||||
expect(isActiveSidebarItem(item, '/sub-sub-link-path')).toBe(true);
|
||||
|
||||
// Ensure it's not trailing slash sensitive:
|
||||
expect(isActiveSidebarItem(item, '/category-path/')).toEqual(true);
|
||||
expect(isActiveSidebarItem(item, '/sub-link-path/')).toEqual(true);
|
||||
expect(isActiveSidebarItem(item, '/sub-category-path/')).toEqual(true);
|
||||
expect(isActiveSidebarItem(item, '/sub-sub-link-path/')).toEqual(true);
|
||||
expect(isActiveSidebarItem(item, '/category-path/')).toBe(true);
|
||||
expect(isActiveSidebarItem(item, '/sub-link-path/')).toBe(true);
|
||||
expect(isActiveSidebarItem(item, '/sub-category-path/')).toBe(true);
|
||||
expect(isActiveSidebarItem(item, '/sub-sub-link-path/')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getBreadcrumbs', () => {
|
||||
describe('useSidebarBreadcrumbs', () => {
|
||||
const createUseSidebarBreadcrumbsMock =
|
||||
(sidebar: PropSidebar, breadcrumbsOption?: boolean) => (location: string) =>
|
||||
renderHook(() => useSidebarBreadcrumbs(), {
|
||||
wrapper: ({children}) => (
|
||||
<StaticRouter location={location}>
|
||||
<Context.Provider
|
||||
// eslint-disable-next-line react/jsx-no-constructed-context-values
|
||||
value={{
|
||||
globalData: {
|
||||
'docusaurus-plugin-content-docs': {
|
||||
default: {path: '/', breadcrumbs: breadcrumbsOption},
|
||||
},
|
||||
},
|
||||
}}>
|
||||
<DocsSidebarProvider sidebar={sidebar}>
|
||||
{children}
|
||||
</DocsSidebarProvider>
|
||||
</Context.Provider>
|
||||
</StaticRouter>
|
||||
),
|
||||
}).result.current;
|
||||
it('returns empty for empty sidebar', () => {
|
||||
expect(
|
||||
getBreadcrumbs({
|
||||
sidebar: [],
|
||||
pathname: '/doesNotExist',
|
||||
}),
|
||||
).toEqual([]);
|
||||
expect(createUseSidebarBreadcrumbsMock([])('/doesNotExist')).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns empty for sidebar but unknown pathname', () => {
|
||||
const sidebar = [testCategory(), testLink()];
|
||||
expect(
|
||||
getBreadcrumbs({
|
||||
sidebar,
|
||||
pathname: '/doesNotExist',
|
||||
}),
|
||||
).toEqual([]);
|
||||
expect(createUseSidebarBreadcrumbsMock(sidebar)('/doesNotExist')).toEqual(
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
it('returns first level category', () => {
|
||||
const pathname = '/somePathName';
|
||||
const sidebar = [testCategory({href: pathname}), testLink()];
|
||||
|
||||
expect(
|
||||
getBreadcrumbs({
|
||||
sidebar,
|
||||
pathname,
|
||||
}),
|
||||
).toEqual([sidebar[0]]);
|
||||
expect(createUseSidebarBreadcrumbsMock(sidebar)(pathname)).toEqual([
|
||||
sidebar[0],
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns first level link', () => {
|
||||
const pathname = '/somePathName';
|
||||
const sidebar = [testCategory(), testLink({href: pathname})];
|
||||
|
||||
expect(
|
||||
getBreadcrumbs({
|
||||
sidebar,
|
||||
pathname,
|
||||
}),
|
||||
).toEqual([sidebar[1]]);
|
||||
expect(createUseSidebarBreadcrumbsMock(sidebar)(pathname)).toEqual([
|
||||
sidebar[1],
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns nested category', () => {
|
||||
|
@ -403,12 +413,11 @@ describe('getBreadcrumbs', () => {
|
|||
testCategory(),
|
||||
];
|
||||
|
||||
expect(
|
||||
getBreadcrumbs({
|
||||
sidebar,
|
||||
pathname,
|
||||
}),
|
||||
).toEqual([categoryLevel1, categoryLevel2, categoryLevel3]);
|
||||
expect(createUseSidebarBreadcrumbsMock(sidebar)(pathname)).toEqual([
|
||||
categoryLevel1,
|
||||
categoryLevel2,
|
||||
categoryLevel3,
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns nested link', () => {
|
||||
|
@ -441,11 +450,75 @@ describe('getBreadcrumbs', () => {
|
|||
testCategory(),
|
||||
];
|
||||
|
||||
expect(
|
||||
getBreadcrumbs({
|
||||
sidebar,
|
||||
pathname,
|
||||
}),
|
||||
).toEqual([categoryLevel1, categoryLevel2, categoryLevel3, link]);
|
||||
expect(createUseSidebarBreadcrumbsMock(sidebar)(pathname)).toEqual([
|
||||
categoryLevel1,
|
||||
categoryLevel2,
|
||||
categoryLevel3,
|
||||
link,
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns null when breadcrumbs disabled', () => {
|
||||
expect(createUseSidebarBreadcrumbsMock([], false)('/foo')).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null when there is no sidebar', () => {
|
||||
expect(createUseSidebarBreadcrumbsMock(null, false)('/foo')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('useCurrentSidebarCategory', () => {
|
||||
const createUseCurrentSidebarCategoryMock =
|
||||
(sidebar?: PropSidebar) => (location: string) =>
|
||||
renderHook(() => useCurrentSidebarCategory(), {
|
||||
wrapper: ({children}) => (
|
||||
<DocsSidebarProvider sidebar={sidebar}>
|
||||
<StaticRouter location={location}>{children}</StaticRouter>
|
||||
</DocsSidebarProvider>
|
||||
),
|
||||
}).result.current;
|
||||
it('works', () => {
|
||||
const category = {
|
||||
type: 'category',
|
||||
href: '/cat',
|
||||
items: [
|
||||
{type: 'link', href: '/cat/foo', label: 'Foo'},
|
||||
{type: 'link', href: '/cat/bar', label: 'Bar'},
|
||||
{type: 'link', href: '/baz', label: 'Baz'},
|
||||
],
|
||||
};
|
||||
const mockUseCurrentSidebarCategory = createUseCurrentSidebarCategoryMock([
|
||||
{type: 'link', href: '/cat/fake', label: 'Fake'},
|
||||
category,
|
||||
]);
|
||||
expect(mockUseCurrentSidebarCategory('/cat')).toEqual(category);
|
||||
});
|
||||
|
||||
it('throws for non-category index page', () => {
|
||||
const category = {
|
||||
type: 'category',
|
||||
items: [
|
||||
{type: 'link', href: '/cat/foo', label: 'Foo'},
|
||||
{type: 'link', href: '/cat/bar', label: 'Bar'},
|
||||
{type: 'link', href: '/baz', label: 'Baz'},
|
||||
],
|
||||
};
|
||||
const mockUseCurrentSidebarCategory = createUseCurrentSidebarCategoryMock([
|
||||
category,
|
||||
]);
|
||||
expect(() => mockUseCurrentSidebarCategory('/cat'))
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Unexpected: sidebar category could not be found for pathname='/cat'.
|
||||
Hook useCurrentSidebarCategory() should only be used on Category pages"
|
||||
`);
|
||||
});
|
||||
|
||||
it('throws when sidebar is missing', () => {
|
||||
const mockUseCurrentSidebarCategory = createUseCurrentSidebarCategoryMock();
|
||||
expect(() =>
|
||||
mockUseCurrentSidebarCategory('/cat'),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Unexpected: cant find current sidebar in context"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* 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 {isMultiColumnFooterLinks} from '../footerUtils';
|
||||
|
||||
describe('isMultiColumnFooterLinks', () => {
|
||||
it('works', () => {
|
||||
expect(
|
||||
isMultiColumnFooterLinks([
|
||||
{
|
||||
title: 'section',
|
||||
items: [
|
||||
{href: '/foo', label: 'Foo'},
|
||||
{href: '/bar', label: 'Bar'},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'section2',
|
||||
items: [
|
||||
{href: '/foo', label: 'Foo2'},
|
||||
{href: '/bar', label: 'Bar2'},
|
||||
],
|
||||
},
|
||||
]),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isMultiColumnFooterLinks([
|
||||
{href: '/foo', label: 'Foo'},
|
||||
{href: '/bar', label: 'Bar'},
|
||||
]),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* 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 {useTitleFormatter} from '../generalUtils';
|
||||
import {renderHook} from '@testing-library/react-hooks';
|
||||
import {Context} from '@docusaurus/docusaurusContext';
|
||||
import type {DocusaurusContext} from '@docusaurus/types';
|
||||
|
||||
describe('useTitleFormatter', () => {
|
||||
const createUseTitleFormatterMock =
|
||||
(context: DocusaurusContext) => (title?: string) =>
|
||||
renderHook(() => useTitleFormatter(title), {
|
||||
wrapper: ({children}) => (
|
||||
<Context.Provider value={context}>{children}</Context.Provider>
|
||||
),
|
||||
}).result.current;
|
||||
it('works', () => {
|
||||
const mockUseTitleFormatter = createUseTitleFormatterMock({
|
||||
siteConfig: {
|
||||
title: 'my site',
|
||||
titleDelimiter: '·',
|
||||
},
|
||||
});
|
||||
expect(mockUseTitleFormatter('a page')).toBe('a page · my site');
|
||||
expect(mockUseTitleFormatter(undefined)).toBe('my site');
|
||||
expect(mockUseTitleFormatter(' ')).toBe('my site');
|
||||
});
|
||||
});
|
|
@ -9,13 +9,13 @@ import {isRegexpStringMatch} from '../regexpUtils';
|
|||
|
||||
describe('isRegexpStringMatch', () => {
|
||||
it('works', () => {
|
||||
expect(isRegexpStringMatch(undefined, 'foo')).toEqual(false);
|
||||
expect(isRegexpStringMatch('bar', undefined)).toEqual(false);
|
||||
expect(isRegexpStringMatch('foo', 'bar')).toEqual(false);
|
||||
expect(isRegexpStringMatch('foo', 'foo')).toEqual(true);
|
||||
expect(isRegexpStringMatch('fooooooooooo', 'foo')).toEqual(false);
|
||||
expect(isRegexpStringMatch('foo', 'fooooooo')).toEqual(true);
|
||||
expect(isRegexpStringMatch('f.*o', 'fggo')).toEqual(true);
|
||||
expect(isRegexpStringMatch('FOO', 'foo')).toEqual(true);
|
||||
expect(isRegexpStringMatch(undefined, 'foo')).toBe(false);
|
||||
expect(isRegexpStringMatch('bar', undefined)).toBe(false);
|
||||
expect(isRegexpStringMatch('foo', 'bar')).toBe(false);
|
||||
expect(isRegexpStringMatch('foo', 'foo')).toBe(true);
|
||||
expect(isRegexpStringMatch('fooooooooooo', 'foo')).toBe(false);
|
||||
expect(isRegexpStringMatch('foo', 'fooooooo')).toBe(true);
|
||||
expect(isRegexpStringMatch('f.*o', 'fggo')).toBe(true);
|
||||
expect(isRegexpStringMatch('FOO', 'foo')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {type Route} from '@generated/routes';
|
||||
import type {Route} from '@docusaurus/types';
|
||||
import {findHomePageRoute} from '../routesUtils';
|
||||
|
||||
describe('findHomePageRoute', () => {
|
||||
|
@ -15,7 +15,7 @@ describe('findHomePageRoute', () => {
|
|||
};
|
||||
|
||||
it('returns undefined for no routes', () => {
|
||||
expect(findHomePageRoute({baseUrl: '/', routes: []})).toEqual(undefined);
|
||||
expect(findHomePageRoute({baseUrl: '/', routes: []})).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns undefined for no homepage', () => {
|
||||
|
@ -37,7 +37,7 @@ describe('findHomePageRoute', () => {
|
|||
},
|
||||
],
|
||||
}),
|
||||
).toEqual(undefined);
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('finds top-level homepage', () => {
|
||||
|
|
|
@ -9,6 +9,6 @@ import {docVersionSearchTag} from '../searchUtils';
|
|||
|
||||
describe('docVersionSearchTag', () => {
|
||||
it('works', () => {
|
||||
expect(docVersionSearchTag('foo', 'bar')).toEqual('docs-foo-bar');
|
||||
expect(docVersionSearchTag('foo', 'bar')).toBe('docs-foo-bar');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,47 +7,50 @@
|
|||
|
||||
import type {TOCItem} from '@docusaurus/types';
|
||||
import {renderHook} from '@testing-library/react-hooks';
|
||||
import {useFilteredAndTreeifiedTOC} from '../tocUtils';
|
||||
import {useFilteredAndTreeifiedTOC, useTreeifiedTOC} from '../tocUtils';
|
||||
|
||||
const mockTOC: TOCItem[] = [
|
||||
{
|
||||
id: 'bravo',
|
||||
level: 2,
|
||||
value: 'Bravo',
|
||||
},
|
||||
{
|
||||
id: 'charlie',
|
||||
level: 3,
|
||||
value: 'Charlie',
|
||||
},
|
||||
{
|
||||
id: 'delta',
|
||||
level: 4,
|
||||
value: 'Delta',
|
||||
},
|
||||
{
|
||||
id: 'echo',
|
||||
level: 5,
|
||||
value: 'Echo',
|
||||
},
|
||||
{
|
||||
id: 'foxtrot',
|
||||
level: 6,
|
||||
value: 'Foxtrot',
|
||||
},
|
||||
];
|
||||
|
||||
describe('useTreeifiedTOC', () => {
|
||||
it('treeifies TOC without filtering', () => {
|
||||
expect(
|
||||
renderHook(() => useTreeifiedTOC(mockTOC)).result.current,
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('useFilteredAndTreeifiedTOC', () => {
|
||||
it('filters a toc with all heading levels', () => {
|
||||
const toc: TOCItem[] = [
|
||||
{
|
||||
id: 'alpha',
|
||||
level: 1,
|
||||
value: 'alpha',
|
||||
},
|
||||
{
|
||||
id: 'bravo',
|
||||
level: 2,
|
||||
value: 'Bravo',
|
||||
},
|
||||
{
|
||||
id: 'charlie',
|
||||
level: 3,
|
||||
value: 'Charlie',
|
||||
},
|
||||
{
|
||||
id: 'delta',
|
||||
level: 4,
|
||||
value: 'Delta',
|
||||
},
|
||||
{
|
||||
id: 'echo',
|
||||
level: 5,
|
||||
value: 'Echo',
|
||||
},
|
||||
{
|
||||
id: 'foxtrot',
|
||||
level: 6,
|
||||
value: 'Foxtrot',
|
||||
},
|
||||
];
|
||||
|
||||
expect(
|
||||
renderHook(() =>
|
||||
useFilteredAndTreeifiedTOC({
|
||||
toc,
|
||||
toc: mockTOC,
|
||||
minHeadingLevel: 2,
|
||||
maxHeadingLevel: 2,
|
||||
}),
|
||||
|
@ -64,7 +67,7 @@ describe('useFilteredAndTreeifiedTOC', () => {
|
|||
expect(
|
||||
renderHook(() =>
|
||||
useFilteredAndTreeifiedTOC({
|
||||
toc,
|
||||
toc: mockTOC,
|
||||
minHeadingLevel: 3,
|
||||
maxHeadingLevel: 3,
|
||||
}),
|
||||
|
@ -81,7 +84,7 @@ describe('useFilteredAndTreeifiedTOC', () => {
|
|||
expect(
|
||||
renderHook(() =>
|
||||
useFilteredAndTreeifiedTOC({
|
||||
toc,
|
||||
toc: mockTOC,
|
||||
minHeadingLevel: 2,
|
||||
maxHeadingLevel: 3,
|
||||
}),
|
||||
|
@ -105,7 +108,7 @@ describe('useFilteredAndTreeifiedTOC', () => {
|
|||
expect(
|
||||
renderHook(() =>
|
||||
useFilteredAndTreeifiedTOC({
|
||||
toc,
|
||||
toc: mockTOC,
|
||||
minHeadingLevel: 2,
|
||||
maxHeadingLevel: 4,
|
||||
}),
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
/**
|
||||
* 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 {useAlternatePageUtils} from '../useAlternatePageUtils';
|
||||
import {renderHook} from '@testing-library/react-hooks';
|
||||
import {StaticRouter} from 'react-router-dom';
|
||||
import {Context} from '@docusaurus/docusaurusContext';
|
||||
import type {DocusaurusContext} from '@docusaurus/types';
|
||||
|
||||
describe('useAlternatePageUtils', () => {
|
||||
const createUseAlternatePageUtilsMock =
|
||||
(context: DocusaurusContext) => (location: string) =>
|
||||
renderHook(() => useAlternatePageUtils(), {
|
||||
wrapper: ({children}) => (
|
||||
<Context.Provider value={context}>
|
||||
<StaticRouter location={location}>{children}</StaticRouter>
|
||||
</Context.Provider>
|
||||
),
|
||||
}).result.current;
|
||||
it('works for baseUrl: / and currentLocale = defaultLocale', () => {
|
||||
const mockUseAlternatePageUtils = createUseAlternatePageUtilsMock({
|
||||
siteConfig: {baseUrl: '/', url: 'https://example.com'},
|
||||
i18n: {defaultLocale: 'en', currentLocale: 'en'},
|
||||
});
|
||||
expect(
|
||||
mockUseAlternatePageUtils('/').createUrl({
|
||||
locale: 'zh-Hans',
|
||||
fullyQualified: false,
|
||||
}),
|
||||
).toBe('/zh-Hans/');
|
||||
expect(
|
||||
mockUseAlternatePageUtils('/foo').createUrl({
|
||||
locale: 'zh-Hans',
|
||||
fullyQualified: false,
|
||||
}),
|
||||
).toBe('/zh-Hans/foo');
|
||||
expect(
|
||||
mockUseAlternatePageUtils('/foo').createUrl({
|
||||
locale: 'zh-Hans',
|
||||
fullyQualified: true,
|
||||
}),
|
||||
).toBe('https://example.com/zh-Hans/foo');
|
||||
});
|
||||
|
||||
it('works for baseUrl: / and currentLocale /= defaultLocale', () => {
|
||||
const mockUseAlternatePageUtils = createUseAlternatePageUtilsMock({
|
||||
siteConfig: {baseUrl: '/zh-Hans/', url: 'https://example.com'},
|
||||
i18n: {defaultLocale: 'en', currentLocale: 'zh-Hans'},
|
||||
});
|
||||
expect(
|
||||
mockUseAlternatePageUtils('/zh-Hans/').createUrl({
|
||||
locale: 'en',
|
||||
fullyQualified: false,
|
||||
}),
|
||||
).toBe('/');
|
||||
expect(
|
||||
mockUseAlternatePageUtils('/zh-Hans/foo').createUrl({
|
||||
locale: 'en',
|
||||
fullyQualified: false,
|
||||
}),
|
||||
).toBe('/foo');
|
||||
expect(
|
||||
mockUseAlternatePageUtils('/zh-Hans/foo').createUrl({
|
||||
locale: 'en',
|
||||
fullyQualified: true,
|
||||
}),
|
||||
).toBe('https://example.com/foo');
|
||||
});
|
||||
|
||||
it('works for non-root base URL and currentLocale = defaultLocale', () => {
|
||||
const mockUseAlternatePageUtils = createUseAlternatePageUtilsMock({
|
||||
siteConfig: {baseUrl: '/base/', url: 'https://example.com'},
|
||||
i18n: {defaultLocale: 'en', currentLocale: 'en'},
|
||||
});
|
||||
expect(
|
||||
mockUseAlternatePageUtils('/base/').createUrl({
|
||||
locale: 'zh-Hans',
|
||||
fullyQualified: false,
|
||||
}),
|
||||
).toBe('/base/zh-Hans/');
|
||||
expect(
|
||||
mockUseAlternatePageUtils('/base/foo').createUrl({
|
||||
locale: 'zh-Hans',
|
||||
fullyQualified: false,
|
||||
}),
|
||||
).toBe('/base/zh-Hans/foo');
|
||||
expect(
|
||||
mockUseAlternatePageUtils('/base/foo').createUrl({
|
||||
locale: 'zh-Hans',
|
||||
fullyQualified: true,
|
||||
}),
|
||||
).toBe('https://example.com/base/zh-Hans/foo');
|
||||
});
|
||||
|
||||
it('works for non-root base URL and currentLocale /= defaultLocale', () => {
|
||||
const mockUseAlternatePageUtils = createUseAlternatePageUtilsMock({
|
||||
siteConfig: {baseUrl: '/base/zh-Hans/', url: 'https://example.com'},
|
||||
i18n: {defaultLocale: 'en', currentLocale: 'zh-Hans'},
|
||||
});
|
||||
expect(
|
||||
mockUseAlternatePageUtils('/base/zh-Hans/').createUrl({
|
||||
locale: 'en',
|
||||
fullyQualified: false,
|
||||
}),
|
||||
).toBe('/base/');
|
||||
expect(
|
||||
mockUseAlternatePageUtils('/base/zh-Hans/foo').createUrl({
|
||||
locale: 'en',
|
||||
fullyQualified: false,
|
||||
}),
|
||||
).toBe('/base/foo');
|
||||
expect(
|
||||
mockUseAlternatePageUtils('/base/zh-Hans/foo').createUrl({
|
||||
locale: 'en',
|
||||
fullyQualified: true,
|
||||
}),
|
||||
).toBe('https://example.com/base/foo');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* 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 {useLocalPathname} from '../useLocalPathname';
|
||||
import {renderHook} from '@testing-library/react-hooks';
|
||||
import {StaticRouter} from 'react-router-dom';
|
||||
import {Context} from '@docusaurus/docusaurusContext';
|
||||
import type {DocusaurusContext} from '@docusaurus/types';
|
||||
|
||||
describe('useLocalPathname', () => {
|
||||
const createUseLocalPathnameMock =
|
||||
(context: DocusaurusContext) => (location: string) =>
|
||||
renderHook(() => useLocalPathname(), {
|
||||
wrapper: ({children}) => (
|
||||
<Context.Provider value={context}>
|
||||
<StaticRouter location={location}>{children}</StaticRouter>
|
||||
</Context.Provider>
|
||||
),
|
||||
}).result.current;
|
||||
it('works for baseUrl: /', () => {
|
||||
const mockUseLocalPathname = createUseLocalPathnameMock({
|
||||
siteConfig: {baseUrl: '/'},
|
||||
});
|
||||
expect(mockUseLocalPathname('/foo')).toBe('/foo');
|
||||
});
|
||||
|
||||
it('works for non-root baseUrl', () => {
|
||||
const mockUseLocalPathname = createUseLocalPathnameMock({
|
||||
siteConfig: {baseUrl: '/base/'},
|
||||
});
|
||||
expect(mockUseLocalPathname('/base/foo')).toBe('/foo');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* 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 {jest} from '@jest/globals';
|
||||
import React from 'react';
|
||||
import {usePluralForm} from '../usePluralForm';
|
||||
import {renderHook} from '@testing-library/react-hooks';
|
||||
import {Context} from '@docusaurus/docusaurusContext';
|
||||
import type {DocusaurusContext} from '@docusaurus/types';
|
||||
|
||||
describe('usePluralForm', () => {
|
||||
const createUsePluralFormMock = (context: DocusaurusContext) => () =>
|
||||
renderHook(() => usePluralForm(), {
|
||||
wrapper: ({children}) => (
|
||||
<Context.Provider value={context}>{children}</Context.Provider>
|
||||
),
|
||||
}).result.current;
|
||||
it('returns the right plural', () => {
|
||||
const mockUsePluralForm = createUsePluralFormMock({
|
||||
i18n: {
|
||||
currentLocale: 'en',
|
||||
},
|
||||
});
|
||||
expect(mockUsePluralForm().selectMessage(1, 'one|many')).toBe('one');
|
||||
expect(mockUsePluralForm().selectMessage(10, 'one|many')).toBe('many');
|
||||
});
|
||||
|
||||
it('warns against too many plurals', () => {
|
||||
const mockUsePluralForm = createUsePluralFormMock({
|
||||
i18n: {
|
||||
currentLocale: 'zh-Hans',
|
||||
},
|
||||
});
|
||||
const consoleMock = jest
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
expect(mockUsePluralForm().selectMessage(1, 'one|many')).toBe('one');
|
||||
expect(mockUsePluralForm().selectMessage(10, 'one|many')).toBe('one');
|
||||
expect(consoleMock.mock.calls[0][0]).toMatchInlineSnapshot(
|
||||
`"For locale=zh-Hans, a maximum of 1 plural forms are expected (other), but the message contains 2: one|many"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('uses the last with not enough plurals', () => {
|
||||
const mockUsePluralForm = createUsePluralFormMock({
|
||||
i18n: {
|
||||
currentLocale: 'en',
|
||||
},
|
||||
});
|
||||
expect(mockUsePluralForm().selectMessage(10, 'many')).toBe('many');
|
||||
});
|
||||
|
||||
it('falls back when Intl.PluralForms is not available', () => {
|
||||
const mockUsePluralForm = createUsePluralFormMock({
|
||||
i18n: {
|
||||
currentLocale: 'zh-Hans',
|
||||
},
|
||||
});
|
||||
const consoleMock = jest
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
const pluralMock = jest
|
||||
.spyOn(Intl, 'PluralRules')
|
||||
.mockImplementation(() => undefined);
|
||||
expect(mockUsePluralForm().selectMessage(1, 'one|many')).toBe('one');
|
||||
expect(mockUsePluralForm().selectMessage(10, 'one|many')).toBe('many');
|
||||
expect(consoleMock.mock.calls[0][0]).toMatchInlineSnapshot(`
|
||||
"Failed to use Intl.PluralRules for locale \\"zh-Hans\\".
|
||||
Docusaurus will fallback to the default (English) implementation.
|
||||
Error: pluralRules.resolvedOptions is not a function
|
||||
"
|
||||
`);
|
||||
pluralMock.mockRestore();
|
||||
});
|
||||
});
|
|
@ -187,7 +187,7 @@ export function isActiveSidebarItem(
|
|||
return false;
|
||||
}
|
||||
|
||||
export function getBreadcrumbs({
|
||||
function getBreadcrumbs({
|
||||
sidebar,
|
||||
pathname,
|
||||
}: {
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
|
||||
export const useTitleFormatter = (title?: string | undefined): string => {
|
||||
export function useTitleFormatter(title?: string | undefined): string {
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
const {title: siteTitle, titleDelimiter} = siteConfig;
|
||||
return title && title.trim().length
|
||||
? `${title.trim()} ${titleDelimiter} ${siteTitle}`
|
||||
: siteTitle;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import GeneratedRoutes, {type Route} from '@generated/routes';
|
||||
import generatedRoutes from '@generated/routes';
|
||||
import {useMemo} from 'react';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import type {Route} from '@docusaurus/types';
|
||||
|
||||
// Note that all sites don't always have a homepage in practice
|
||||
// See https://github.com/facebook/docusaurus/pull/6517#issuecomment-1048709116
|
||||
|
@ -48,7 +49,7 @@ export function useHomePageRoute(): Route | undefined {
|
|||
return useMemo(
|
||||
() =>
|
||||
findHomePageRoute({
|
||||
routes: GeneratedRoutes,
|
||||
routes: generatedRoutes,
|
||||
baseUrl,
|
||||
}),
|
||||
[baseUrl],
|
||||
|
|
|
@ -70,21 +70,13 @@ function useLocalePluralForms(): LocalePluralForms {
|
|||
i18n: {currentLocale},
|
||||
} = useDocusaurusContext();
|
||||
return useMemo(() => {
|
||||
// @ts-expect-error checking Intl.PluralRules in case browser doesn't
|
||||
// have it (e.g Safari 12-)
|
||||
if (Intl.PluralRules) {
|
||||
try {
|
||||
return createLocalePluralForms(currentLocale);
|
||||
} catch {
|
||||
console.error(`Failed to use Intl.PluralRules for locale "${currentLocale}".
|
||||
Docusaurus will fallback to a default/fallback (English) Intl.PluralRules implementation.
|
||||
try {
|
||||
return createLocalePluralForms(currentLocale);
|
||||
} catch (err) {
|
||||
console.error(`Failed to use Intl.PluralRules for locale "${currentLocale}".
|
||||
Docusaurus will fallback to the default (English) implementation.
|
||||
Error: ${(err as Error).message}
|
||||
`);
|
||||
return EnglishPluralForms;
|
||||
}
|
||||
} else {
|
||||
console.error(`Intl.PluralRules not available!
|
||||
Docusaurus will fallback to a default/fallback (English) Intl.PluralRules implementation.
|
||||
`);
|
||||
return EnglishPluralForms;
|
||||
}
|
||||
}, [currentLocale]);
|
||||
|
@ -103,7 +95,7 @@ function selectPluralMessage(
|
|||
}
|
||||
if (parts.length > localePluralForms.pluralForms.length) {
|
||||
console.error(
|
||||
`For locale=${localePluralForms.locale}, a maximum of ${localePluralForms.pluralForms.length} plural forms are expected (${localePluralForms.pluralForms}), but the message contains ${parts.length} plural forms: ${pluralMessages} `,
|
||||
`For locale=${localePluralForms.locale}, a maximum of ${localePluralForms.pluralForms.length} plural forms are expected (${localePluralForms.pluralForms}), but the message contains ${parts.length}: ${pluralMessages}`,
|
||||
);
|
||||
}
|
||||
const pluralForm = localePluralForms.select(count);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue