test(theme-common): improve test coverage (#6902)

* test(theme-common): improve test coverage

* revert
This commit is contained in:
Joshua Chen 2022-03-12 13:17:21 +08:00 committed by GitHub
parent aa5a2d4c04
commit f6baaa6b75
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 1183 additions and 755 deletions

View file

@ -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",
},
]
`;

View file

@ -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();
});

View file

@ -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"`,
);
});
});

View file

@ -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);
});
});

View file

@ -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');
});
});

View file

@ -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);
});
});

View file

@ -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', () => {

View file

@ -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');
});
});

View file

@ -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,
}),

View file

@ -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');
});
});

View file

@ -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');
});
});

View file

@ -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();
});
});

View file

@ -187,7 +187,7 @@ export function isActiveSidebarItem(
return false;
}
export function getBreadcrumbs({
function getBreadcrumbs({
sidebar,
pathname,
}: {

View file

@ -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;
};
}

View file

@ -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],

View file

@ -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);