mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 23:57:22 +02:00
test: strengthen internal types (#7488)
This commit is contained in:
parent
d50fe3b670
commit
cd21a31005
15 changed files with 67 additions and 55 deletions
13
jest/deps.d.ts
vendored
13
jest/deps.d.ts
vendored
|
@ -7,9 +7,18 @@
|
||||||
|
|
||||||
// modules only used in tests
|
// modules only used in tests
|
||||||
|
|
||||||
declare module 'to-vfile';
|
declare module 'to-vfile' {
|
||||||
|
import type {VFile} from 'vfile';
|
||||||
|
|
||||||
declare module 'remark-mdx';
|
export function read(path: string, encoding?: string): Promise<VFile>;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'remark-mdx' {
|
||||||
|
import type {Plugin} from 'unified';
|
||||||
|
|
||||||
|
const mdx: Plugin;
|
||||||
|
export = mdx;
|
||||||
|
}
|
||||||
|
|
||||||
declare module '@testing-utils/git' {
|
declare module '@testing-utils/git' {
|
||||||
const createTempRepo: typeof import('./utils/git').createTempRepo;
|
const createTempRepo: typeof import('./utils/git').createTempRepo;
|
||||||
|
|
|
@ -10,8 +10,8 @@ import vfile from 'to-vfile';
|
||||||
import postcss from 'postcss';
|
import postcss from 'postcss';
|
||||||
import postCssRemoveOverriddenCustomProperties from '../index';
|
import postCssRemoveOverriddenCustomProperties from '../index';
|
||||||
|
|
||||||
const processFixture = (name: string) => {
|
const processFixture = async (name: string) => {
|
||||||
const input = vfile.readSync(
|
const input = await vfile.read(
|
||||||
path.join(__dirname, '__fixtures__', `${name}.css`),
|
path.join(__dirname, '__fixtures__', `${name}.css`),
|
||||||
'utf8',
|
'utf8',
|
||||||
);
|
);
|
||||||
|
@ -23,11 +23,11 @@ const processFixture = (name: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('remove-overridden-custom-properties', () => {
|
describe('remove-overridden-custom-properties', () => {
|
||||||
it('overridden custom properties should be removed', () => {
|
it('overridden custom properties should be removed', async () => {
|
||||||
expect(processFixture('normal')).toMatchSnapshot();
|
await expect(processFixture('normal')).resolves.toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('overridden custom properties with `!important` rule should not be removed', () => {
|
it('overridden custom properties with `!important` rule should not be removed', async () => {
|
||||||
expect(processFixture('important_rule')).toMatchSnapshot();
|
await expect(processFixture('important_rule')).resolves.toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -552,7 +552,7 @@ async function migrateVersionedSidebar(
|
||||||
(topLevel: SidebarEntries, value) => {
|
(topLevel: SidebarEntries, value) => {
|
||||||
const key = value[0].replace(versionRegex, '');
|
const key = value[0].replace(versionRegex, '');
|
||||||
topLevel[key] = Object.entries(value[1]).reduce<{
|
topLevel[key] = Object.entries(value[1]).reduce<{
|
||||||
[key: string]: Array<string | {[key: string]: unknown}>;
|
[key: string]: (string | {[key: string]: unknown})[];
|
||||||
}>((acc, val) => {
|
}>((acc, val) => {
|
||||||
acc[val[0].replace(versionRegex, '')] = (
|
acc[val[0].replace(versionRegex, '')] = (
|
||||||
val[1] as SidebarEntry[]
|
val[1] as SidebarEntry[]
|
||||||
|
|
|
@ -34,7 +34,7 @@ export type SidebarEntry =
|
||||||
export type SidebarEntries = {
|
export type SidebarEntries = {
|
||||||
[key: string]:
|
[key: string]:
|
||||||
| {[key: string]: unknown}
|
| {[key: string]: unknown}
|
||||||
| Array<{[key: string]: unknown} | string>;
|
| ({[key: string]: unknown} | string)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type VersionTwoConfig = {
|
export type VersionTwoConfig = {
|
||||||
|
@ -49,7 +49,7 @@ export type VersionTwoConfig = {
|
||||||
githubHost?: string;
|
githubHost?: string;
|
||||||
onBrokenLinks: string;
|
onBrokenLinks: string;
|
||||||
onBrokenMarkdownLinks: string;
|
onBrokenMarkdownLinks: string;
|
||||||
plugins: Array<[string, {[key: string]: unknown}]>;
|
plugins: [string, {[key: string]: unknown}][];
|
||||||
themes?: [];
|
themes?: [];
|
||||||
presets: [[string, ClassicPresetEntries]];
|
presets: [[string, ClassicPresetEntries]];
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
|
@ -58,17 +58,14 @@ export type VersionTwoConfig = {
|
||||||
logo?: {
|
logo?: {
|
||||||
src?: string;
|
src?: string;
|
||||||
};
|
};
|
||||||
items: Array<{[key: string]: unknown} | null>;
|
items: ({[key: string]: unknown} | null)[];
|
||||||
};
|
};
|
||||||
image?: string;
|
image?: string;
|
||||||
footer: {
|
footer: {
|
||||||
links: Array<{
|
links: {
|
||||||
title: string;
|
title: string;
|
||||||
items: Array<{
|
items: {label: string; to: string}[];
|
||||||
label: string;
|
}[];
|
||||||
to: string;
|
|
||||||
}>;
|
|
||||||
}>;
|
|
||||||
copyright?: string;
|
copyright?: string;
|
||||||
logo: {
|
logo: {
|
||||||
src?: string;
|
src?: string;
|
||||||
|
@ -104,26 +101,26 @@ export type VersionOneConfig = {
|
||||||
organizationName?: string;
|
organizationName?: string;
|
||||||
projectName?: string;
|
projectName?: string;
|
||||||
noIndex?: boolean;
|
noIndex?: boolean;
|
||||||
headerLinks?: Array<{doc: string; href: string; label: string; page: string}>;
|
headerLinks?: {doc: string; href: string; label: string; page: string}[];
|
||||||
headerIcon?: string;
|
headerIcon?: string;
|
||||||
favicon?: string;
|
favicon?: string;
|
||||||
colors?: {primaryColor: string};
|
colors?: {primaryColor: string};
|
||||||
copyright?: string;
|
copyright?: string;
|
||||||
editUrl?: string;
|
editUrl?: string;
|
||||||
customDocsPath?: string;
|
customDocsPath?: string;
|
||||||
users?: Array<{[key: string]: unknown}>;
|
users?: {[key: string]: unknown}[];
|
||||||
disableHeaderTitle?: string;
|
disableHeaderTitle?: string;
|
||||||
disableTitleTagline?: string;
|
disableTitleTagline?: string;
|
||||||
separateCss?: Array<{[key: string]: unknown}>;
|
separateCss?: {[key: string]: unknown}[];
|
||||||
footerIcon?: string;
|
footerIcon?: string;
|
||||||
translationRecruitingLink?: string;
|
translationRecruitingLink?: string;
|
||||||
algolia?: {[key: string]: unknown};
|
algolia?: {[key: string]: unknown};
|
||||||
gaTrackingId?: string;
|
gaTrackingId?: string;
|
||||||
gaGtag?: boolean;
|
gaGtag?: boolean;
|
||||||
highlight?: {[key: string]: unknown};
|
highlight?: {[key: string]: unknown};
|
||||||
markdownPlugins?: Array<() => void>;
|
markdownPlugins?: (() => void)[];
|
||||||
scripts?: Array<{src: string; [key: string]: unknown} | string>;
|
scripts?: ({src: string; [key: string]: unknown} | string)[];
|
||||||
stylesheets?: Array<{href: string; [key: string]: unknown} | string>;
|
stylesheets?: ({href: string; [key: string]: unknown} | string)[];
|
||||||
facebookAppId?: string;
|
facebookAppId?: string;
|
||||||
facebookComments?: true;
|
facebookComments?: true;
|
||||||
facebookPixelId?: string;
|
facebookPixelId?: string;
|
||||||
|
|
|
@ -31,6 +31,7 @@ import type {
|
||||||
Options,
|
Options,
|
||||||
PluginOptions,
|
PluginOptions,
|
||||||
PropSidebarItemLink,
|
PropSidebarItemLink,
|
||||||
|
PropSidebars,
|
||||||
} from '@docusaurus/plugin-content-docs';
|
} from '@docusaurus/plugin-content-docs';
|
||||||
import type {
|
import type {
|
||||||
SidebarItemsGeneratorOption,
|
SidebarItemsGeneratorOption,
|
||||||
|
@ -82,7 +83,7 @@ const createFakeActions = (contentDir: string) => {
|
||||||
|
|
||||||
// Query by prefix, because files have a hash at the end so it's not
|
// Query by prefix, because files have a hash at the end so it's not
|
||||||
// convenient to query by full filename
|
// convenient to query by full filename
|
||||||
const getCreatedDataByPrefix = (prefix: string) => {
|
function getCreatedDataByPrefix(prefix: string) {
|
||||||
const entry = Object.entries(dataContainer).find(([key]) =>
|
const entry = Object.entries(dataContainer).find(([key]) =>
|
||||||
key.startsWith(prefix),
|
key.startsWith(prefix),
|
||||||
);
|
);
|
||||||
|
@ -92,8 +93,8 @@ Entries created:
|
||||||
- ${Object.keys(dataContainer).join('\n- ')}
|
- ${Object.keys(dataContainer).join('\n- ')}
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
return JSON.parse(entry[1] as string);
|
return JSON.parse(entry[1] as string) as PropSidebars;
|
||||||
};
|
}
|
||||||
|
|
||||||
// Extra fns useful for tests!
|
// Extra fns useful for tests!
|
||||||
const utils = {
|
const utils = {
|
||||||
|
@ -571,8 +572,8 @@ describe('versioned website (community)', () => {
|
||||||
allContent: {},
|
allContent: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
utils.checkVersionMetadataPropCreated(currentVersion!);
|
utils.checkVersionMetadataPropCreated(currentVersion);
|
||||||
utils.checkVersionMetadataPropCreated(version100!);
|
utils.checkVersionMetadataPropCreated(version100);
|
||||||
|
|
||||||
utils.expectSnapshot();
|
utils.expectSnapshot();
|
||||||
});
|
});
|
||||||
|
|
|
@ -71,6 +71,6 @@ export class ReactContextError extends Error {
|
||||||
this.message = `Hook ${
|
this.message = `Hook ${
|
||||||
this.stack?.split('\n')[1]?.match(/at (?:\w+\.)?(?<name>\w+)/)?.groups!
|
this.stack?.split('\n')[1]?.match(/at (?:\w+\.)?(?<name>\w+)/)?.groups!
|
||||||
.name ?? ''
|
.name ?? ''
|
||||||
} is called outside the <${providerName}>. ${additionalInfo || ''}`;
|
} is called outside the <${providerName}>. ${additionalInfo ?? ''}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,7 +120,9 @@ function DocSearch({
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
import('@docsearch/react/modal'),
|
import('@docsearch/react/modal') as Promise<
|
||||||
|
typeof import('@docsearch/react')
|
||||||
|
>,
|
||||||
import('@docsearch/react/style'),
|
import('@docsearch/react/style'),
|
||||||
import('./styles.css'),
|
import('./styles.css'),
|
||||||
]).then(([{DocSearchModal: Modal}]) => {
|
]).then(([{DocSearchModal: Modal}]) => {
|
||||||
|
|
|
@ -42,7 +42,7 @@ describe('normalizePluginOptions', () => {
|
||||||
it('throws for invalid options', () => {
|
it('throws for invalid options', () => {
|
||||||
const options = {foo: 1};
|
const options = {foo: 1};
|
||||||
expect(() =>
|
expect(() =>
|
||||||
normalizePluginOptions(Joi.object({foo: Joi.string()}), options),
|
normalizePluginOptions(Joi.object<object>({foo: Joi.string()}), options),
|
||||||
).toThrowErrorMatchingInlineSnapshot(`""foo" must be a string"`);
|
).toThrowErrorMatchingInlineSnapshot(`""foo" must be a string"`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -90,7 +90,10 @@ describe('normalizeThemeConfig', () => {
|
||||||
it('throws for invalid options', () => {
|
it('throws for invalid options', () => {
|
||||||
const themeConfig = {foo: 1, bar: 1};
|
const themeConfig = {foo: 1, bar: 1};
|
||||||
expect(() =>
|
expect(() =>
|
||||||
normalizeThemeConfig(Joi.object({foo: Joi.string()}), themeConfig),
|
normalizeThemeConfig(
|
||||||
|
Joi.object<object>({foo: Joi.string()}),
|
||||||
|
themeConfig,
|
||||||
|
),
|
||||||
).toThrowErrorMatchingInlineSnapshot(`""foo" must be a string"`);
|
).toThrowErrorMatchingInlineSnapshot(`""foo" must be a string"`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ class PendingNavigation extends React.Component<Props, State> {
|
||||||
? dispatchLifecycleAction('onRouteUpdate', {
|
? dispatchLifecycleAction('onRouteUpdate', {
|
||||||
previousLocation: null,
|
previousLocation: null,
|
||||||
location: this.props.location,
|
location: this.props.location,
|
||||||
})!
|
})
|
||||||
: () => {};
|
: () => {};
|
||||||
this.state = {
|
this.state = {
|
||||||
nextRouteHasLoaded: true,
|
nextRouteHasLoaded: true,
|
||||||
|
@ -60,14 +60,14 @@ class PendingNavigation extends React.Component<Props, State> {
|
||||||
this.routeUpdateCleanupCb = dispatchLifecycleAction('onRouteUpdate', {
|
this.routeUpdateCleanupCb = dispatchLifecycleAction('onRouteUpdate', {
|
||||||
previousLocation: this.previousLocation,
|
previousLocation: this.previousLocation,
|
||||||
location: nextLocation,
|
location: nextLocation,
|
||||||
})!;
|
});
|
||||||
|
|
||||||
// Load data while the old screen remains. Force preload instead of using
|
// Load data while the old screen remains. Force preload instead of using
|
||||||
// `window.docusaurus`, because we want to avoid loading screen even when
|
// `window.docusaurus`, because we want to avoid loading screen even when
|
||||||
// user is on saveData
|
// user is on saveData
|
||||||
preload(nextLocation.pathname)
|
preload(nextLocation.pathname)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.routeUpdateCleanupCb?.();
|
this.routeUpdateCleanupCb();
|
||||||
this.setState({nextRouteHasLoaded: true});
|
this.setState({nextRouteHasLoaded: true});
|
||||||
})
|
})
|
||||||
.catch((e: unknown) => console.warn(e));
|
.catch((e: unknown) => console.warn(e));
|
||||||
|
|
|
@ -69,7 +69,7 @@ This behavior can have SEO impacts and create relative link issues.
|
||||||
|
|
||||||
// The source branch; defaults to the currently checked out branch
|
// The source branch; defaults to the currently checked out branch
|
||||||
const sourceBranch =
|
const sourceBranch =
|
||||||
process.env.CURRENT_BRANCH ||
|
process.env.CURRENT_BRANCH ??
|
||||||
shell.exec('git rev-parse --abbrev-ref HEAD', {silent: true}).stdout.trim();
|
shell.exec('git rev-parse --abbrev-ref HEAD', {silent: true}).stdout.trim();
|
||||||
|
|
||||||
const gitUser = process.env.GIT_USER;
|
const gitUser = process.env.GIT_USER;
|
||||||
|
@ -90,8 +90,8 @@ This behavior can have SEO impacts and create relative link issues.
|
||||||
}
|
}
|
||||||
|
|
||||||
const organizationName =
|
const organizationName =
|
||||||
process.env.ORGANIZATION_NAME ||
|
process.env.ORGANIZATION_NAME ??
|
||||||
process.env.CIRCLE_PROJECT_USERNAME ||
|
process.env.CIRCLE_PROJECT_USERNAME ??
|
||||||
siteConfig.organizationName;
|
siteConfig.organizationName;
|
||||||
if (!organizationName) {
|
if (!organizationName) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -101,8 +101,8 @@ This behavior can have SEO impacts and create relative link issues.
|
||||||
logger.info`organizationName: name=${organizationName}`;
|
logger.info`organizationName: name=${organizationName}`;
|
||||||
|
|
||||||
const projectName =
|
const projectName =
|
||||||
process.env.PROJECT_NAME ||
|
process.env.PROJECT_NAME ??
|
||||||
process.env.CIRCLE_PROJECT_REPONAME ||
|
process.env.CIRCLE_PROJECT_REPONAME ??
|
||||||
siteConfig.projectName;
|
siteConfig.projectName;
|
||||||
if (!projectName) {
|
if (!projectName) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -113,7 +113,7 @@ This behavior can have SEO impacts and create relative link issues.
|
||||||
|
|
||||||
// We never deploy on pull request.
|
// We never deploy on pull request.
|
||||||
const isPullRequest =
|
const isPullRequest =
|
||||||
process.env.CI_PULL_REQUEST || process.env.CIRCLE_PULL_REQUEST;
|
process.env.CI_PULL_REQUEST ?? process.env.CIRCLE_PULL_REQUEST;
|
||||||
if (isPullRequest) {
|
if (isPullRequest) {
|
||||||
shell.echo('Skipping deploy on a pull request.');
|
shell.echo('Skipping deploy on a pull request.');
|
||||||
shell.exit(0);
|
shell.exit(0);
|
||||||
|
@ -136,12 +136,12 @@ You can also set the deploymentBranch property in docusaurus.config.js .`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const deploymentBranch =
|
const deploymentBranch =
|
||||||
process.env.DEPLOYMENT_BRANCH || siteConfig.deploymentBranch || 'gh-pages';
|
process.env.DEPLOYMENT_BRANCH ?? siteConfig.deploymentBranch ?? 'gh-pages';
|
||||||
logger.info`deploymentBranch: name=${deploymentBranch}`;
|
logger.info`deploymentBranch: name=${deploymentBranch}`;
|
||||||
|
|
||||||
const githubHost =
|
const githubHost =
|
||||||
process.env.GITHUB_HOST || siteConfig.githubHost || 'github.com';
|
process.env.GITHUB_HOST ?? siteConfig.githubHost ?? 'github.com';
|
||||||
const githubPort = process.env.GITHUB_PORT || siteConfig.githubPort;
|
const githubPort = process.env.GITHUB_PORT ?? siteConfig.githubPort;
|
||||||
|
|
||||||
let deploymentRepoURL: string;
|
let deploymentRepoURL: string;
|
||||||
if (useSSH) {
|
if (useSSH) {
|
||||||
|
@ -214,7 +214,7 @@ You can also set the deploymentBranch property in docusaurus.config.js .`);
|
||||||
shellExecLog('git add --all');
|
shellExecLog('git add --all');
|
||||||
|
|
||||||
const commitMessage =
|
const commitMessage =
|
||||||
process.env.CUSTOM_COMMIT_MESSAGE ||
|
process.env.CUSTOM_COMMIT_MESSAGE ??
|
||||||
`Deploy website - based on ${currentCommit}`;
|
`Deploy website - based on ${currentCommit}`;
|
||||||
const commitResults = shellExecLog(`git commit -m "${commitMessage}"`);
|
const commitResults = shellExecLog(`git commit -m "${commitMessage}"`);
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -65,7 +65,7 @@ function createExitMock() {
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line jest/require-top-level-describe
|
// eslint-disable-next-line jest/require-top-level-describe
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
mock?.mockRestore();
|
mock.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -28,7 +28,7 @@ describe('loadPlugins', () => {
|
||||||
name: 'test1',
|
name: 'test1',
|
||||||
prop: 'a',
|
prop: 'a',
|
||||||
async loadContent() {
|
async loadContent() {
|
||||||
// Testing that plugin lifecycle is bound to the plugin instance
|
// Testing that plugin lifecycle is bound to the instance
|
||||||
return this.prop;
|
return this.prop;
|
||||||
},
|
},
|
||||||
async contentLoaded({content, actions}) {
|
async contentLoaded({content, actions}) {
|
||||||
|
|
|
@ -96,14 +96,14 @@ type SidebarGenerator = (generatorArgs: {
|
||||||
/** Useful metadata for the version this sidebar belongs to. */
|
/** Useful metadata for the version this sidebar belongs to. */
|
||||||
version: {contentPath: string; versionName: string};
|
version: {contentPath: string; versionName: string};
|
||||||
/** All the docs of that version (unfiltered). */
|
/** All the docs of that version (unfiltered). */
|
||||||
docs: Array<{
|
docs: {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
frontMatter: DocFrontMatter & Record<string, unknown>;
|
frontMatter: DocFrontMatter & Record<string, unknown>;
|
||||||
source: string;
|
source: string;
|
||||||
sourceDirName: string;
|
sourceDirName: string;
|
||||||
sidebarPosition?: number | undefined;
|
sidebarPosition?: number | undefined;
|
||||||
}>;
|
}[];
|
||||||
/** Number prefix parser configured for this plugin. */
|
/** Number prefix parser configured for this plugin. */
|
||||||
numberPrefixParser: PrefixParser;
|
numberPrefixParser: PrefixParser;
|
||||||
/** The default category index matcher which you can override. */
|
/** The default category index matcher which you can override. */
|
||||||
|
|
|
@ -112,8 +112,8 @@ Turn debug mode on:
|
||||||
|
|
||||||
### `offlineModeActivationStrategies` {#offlinemodeactivationstrategies}
|
### `offlineModeActivationStrategies` {#offlinemodeactivationstrategies}
|
||||||
|
|
||||||
- Type: `Array<'appInstalled' | 'mobile' | 'saveData'| 'queryString' | 'always'>`
|
- Type: `('appInstalled' | 'mobile' | 'saveData'| 'queryString' | 'always')[]`
|
||||||
- Default: `['appInstalled','queryString','standalone']`
|
- Default: `['appInstalled', 'queryString', 'standalone']`
|
||||||
|
|
||||||
Strategies used to turn the offline mode on:
|
Strategies used to turn the offline mode on:
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ The default theme includes an implementation for the reload popup and uses [Infi
|
||||||
|
|
||||||
### `pwaHead` {#pwahead}
|
### `pwaHead` {#pwahead}
|
||||||
|
|
||||||
- Type: `Array<{ tagName: string } & Record<string,string>>`
|
- Type: `({ tagName: string; [attributeName: string]: string })[]`
|
||||||
- Default: `[]`
|
- Default: `[]`
|
||||||
|
|
||||||
Array of objects containing `tagName` and key-value pairs for attributes to inject into the `<head>` tag. Technically you can inject any head tag through this, but it's ideally used for tags to make your site PWA compliant. Here's a list of tag to make your app fully compliant:
|
Array of objects containing `tagName` and key-value pairs for attributes to inject into the `<head>` tag. Technically you can inject any head tag through this, but it's ideally used for tags to make your site PWA compliant. Here's a list of tag to make your app fully compliant:
|
||||||
|
|
|
@ -83,11 +83,11 @@ import TOCInline from '@theme/TOCInline';
|
||||||
The `toc` global is just a list of heading items:
|
The `toc` global is just a list of heading items:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
declare const toc: Array<{
|
declare const toc: {
|
||||||
value: string;
|
value: string;
|
||||||
id: string;
|
id: string;
|
||||||
level: number;
|
level: number;
|
||||||
}>;
|
}[];
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that the `toc` global is a flat array, so you can easily cut out unwanted nodes or insert extra nodes, and create a new TOC tree.
|
Note that the `toc` global is a flat array, so you can easily cut out unwanted nodes or insert extra nodes, and create a new TOC tree.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue