fix(types): fix type of PluginModule (#10420)

This commit is contained in:
Sébastien Lorber 2024-08-19 18:30:52 +02:00 committed by GitHub
parent 0b640ca3e2
commit 2d55ca474e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 445 additions and 366 deletions

View file

@ -191,9 +191,8 @@ export type LoadedPlugin = InitializedPlugin & {
export type PluginModule<Content = unknown> = {
(context: LoadContext, options: unknown):
| Plugin<Content>
| Promise<Plugin<Content>>
| null
| Promise<null>;
| Promise<Plugin<Content> | null>;
validateOptions?: <T, U>(data: OptionValidationContext<T, U>) => U;
validateThemeConfig?: <T>(data: ThemeConfigValidationContext<T>) => T;

View file

@ -1,143 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`normalizeConfig should throw error if plugins is not a string and it's not an array #1 for the input of: [123] 1`] = `
" => Bad Docusaurus plugin value plugins[0].
Example valid plugin config:
{
plugins: [
["@docusaurus/plugin-content-docs",options],
"./myPlugin",
["./myPlugin",{someOption: 42}],
function myPlugin() { },
[function myPlugin() { },options]
],
};
"
`;
exports[`normalizeConfig should throw error if plugins is not an array of [string, object][] #1 for the input of: [[Array]] 1`] = `
" => Bad Docusaurus plugin value plugins[0].
Example valid plugin config:
{
plugins: [
["@docusaurus/plugin-content-docs",options],
"./myPlugin",
["./myPlugin",{someOption: 42}],
function myPlugin() { },
[function myPlugin() { },options]
],
};
"
`;
exports[`normalizeConfig should throw error if plugins is not an array of [string, object][] #2 for the input of: [[Array]] 1`] = `
" => Bad Docusaurus plugin value plugins[0].
Example valid plugin config:
{
plugins: [
["@docusaurus/plugin-content-docs",options],
"./myPlugin",
["./myPlugin",{someOption: 42}],
function myPlugin() { },
[function myPlugin() { },options]
],
};
"
`;
exports[`normalizeConfig should throw error if plugins is not an array of [string, object][] #3 for the input of: [[Array]] 1`] = `
" => Bad Docusaurus plugin value plugins[0].
Example valid plugin config:
{
plugins: [
["@docusaurus/plugin-content-docs",options],
"./myPlugin",
["./myPlugin",{someOption: 42}],
function myPlugin() { },
[function myPlugin() { },options]
],
};
"
`;
exports[`normalizeConfig should throw error if plugins is not array for the input of: {} 1`] = `
""plugins" must be an array
"
`;
exports[`normalizeConfig should throw error if themes is not a string and it's not an array #1 for the input of: [123] 1`] = `
" => Bad Docusaurus theme value themes[0].
Example valid theme config:
{
themes: [
["@docusaurus/theme-classic",options],
"./myTheme",
["./myTheme",{someOption: 42}],
function myTheme() { },
[function myTheme() { },options]
],
};
"
`;
exports[`normalizeConfig should throw error if themes is not an array of [string, object][] #1 for the input of: [[Array]] 1`] = `
" => Bad Docusaurus theme value themes[0].
Example valid theme config:
{
themes: [
["@docusaurus/theme-classic",options],
"./myTheme",
["./myTheme",{someOption: 42}],
function myTheme() { },
[function myTheme() { },options]
],
};
"
`;
exports[`normalizeConfig should throw error if themes is not an array of [string, object][] #2 for the input of: [[Array]] 1`] = `
" => Bad Docusaurus theme value themes[0].
Example valid theme config:
{
themes: [
["@docusaurus/theme-classic",options],
"./myTheme",
["./myTheme",{someOption: 42}],
function myTheme() { },
[function myTheme() { },options]
],
};
"
`;
exports[`normalizeConfig should throw error if themes is not an array of [string, object][] #3 for the input of: [[Array]] 1`] = `
" => Bad Docusaurus theme value themes[0].
Example valid theme config:
{
themes: [
["@docusaurus/theme-classic",options],
"./myTheme",
["./myTheme",{someOption: 42}],
function myTheme() { },
[function myTheme() { },options]
],
};
"
`;
exports[`normalizeConfig should throw error if themes is not array for the input of: {} 1`] = `
""themes" must be an array
"
`;
exports[`normalizeConfig throws error for required fields 1`] = `
""baseUrl" is required
"title" is required
@ -157,7 +19,145 @@ If you still want these fields to be in your configuration, put them in the "cus
See https://docusaurus.io/docs/api/docusaurus-config/#customfields"
`;
exports[`normalizeConfig throws for "error" reporting severity 1`] = `
exports[`onBrokenLinks throws for "error" reporting severity 1`] = `
""onBrokenLinks" must be one of [ignore, log, warn, throw]
"
`;
exports[`plugins should throw error if plugins is not a string and it's not an array #1 for the input of: [123] 1`] = `
" => Bad Docusaurus plugin value plugins[0].
Example valid plugin config:
{
plugins: [
["@docusaurus/plugin-content-docs",options],
"./myPlugin",
["./myPlugin",{someOption: 42}],
function myPlugin() { },
[function myPlugin() { },options]
],
};
"
`;
exports[`plugins should throw error if plugins is not an array of [string, object][] #1 for the input of: [[Array]] 1`] = `
" => Bad Docusaurus plugin value plugins[0].
Example valid plugin config:
{
plugins: [
["@docusaurus/plugin-content-docs",options],
"./myPlugin",
["./myPlugin",{someOption: 42}],
function myPlugin() { },
[function myPlugin() { },options]
],
};
"
`;
exports[`plugins should throw error if plugins is not an array of [string, object][] #2 for the input of: [[Array]] 1`] = `
" => Bad Docusaurus plugin value plugins[0].
Example valid plugin config:
{
plugins: [
["@docusaurus/plugin-content-docs",options],
"./myPlugin",
["./myPlugin",{someOption: 42}],
function myPlugin() { },
[function myPlugin() { },options]
],
};
"
`;
exports[`plugins should throw error if plugins is not an array of [string, object][] #3 for the input of: [[Array]] 1`] = `
" => Bad Docusaurus plugin value plugins[0].
Example valid plugin config:
{
plugins: [
["@docusaurus/plugin-content-docs",options],
"./myPlugin",
["./myPlugin",{someOption: 42}],
function myPlugin() { },
[function myPlugin() { },options]
],
};
"
`;
exports[`plugins should throw error if plugins is not array for the input of: {} 1`] = `
""plugins" must be an array
"
`;
exports[`themes should throw error if themes is not a string and it's not an array #1 for the input of: [123] 1`] = `
" => Bad Docusaurus theme value themes[0].
Example valid theme config:
{
themes: [
["@docusaurus/theme-classic",options],
"./myTheme",
["./myTheme",{someOption: 42}],
function myTheme() { },
[function myTheme() { },options]
],
};
"
`;
exports[`themes should throw error if themes is not an array of [string, object][] #1 for the input of: [[Array]] 1`] = `
" => Bad Docusaurus theme value themes[0].
Example valid theme config:
{
themes: [
["@docusaurus/theme-classic",options],
"./myTheme",
["./myTheme",{someOption: 42}],
function myTheme() { },
[function myTheme() { },options]
],
};
"
`;
exports[`themes should throw error if themes is not an array of [string, object][] #2 for the input of: [[Array]] 1`] = `
" => Bad Docusaurus theme value themes[0].
Example valid theme config:
{
themes: [
["@docusaurus/theme-classic",options],
"./myTheme",
["./myTheme",{someOption: 42}],
function myTheme() { },
[function myTheme() { },options]
],
};
"
`;
exports[`themes should throw error if themes is not an array of [string, object][] #3 for the input of: [[Array]] 1`] = `
" => Bad Docusaurus theme value themes[0].
Example valid theme config:
{
themes: [
["@docusaurus/theme-classic",options],
"./myTheme",
["./myTheme",{someOption: 42}],
function myTheme() { },
[function myTheme() { },options]
],
};
"
`;
exports[`themes should throw error if themes is not array for the input of: {} 1`] = `
""themes" must be an array
"
`;

View file

@ -12,7 +12,7 @@ import {
validateConfig,
} from '../configValidation';
import type {StorageConfig} from '@docusaurus/types/src/config';
import type {Config, DocusaurusConfig} from '@docusaurus/types';
import type {Config, DocusaurusConfig, PluginConfig} from '@docusaurus/types';
import type {DeepPartial} from 'utility-types';
const baseConfig = {
@ -114,6 +114,34 @@ describe('normalizeConfig', () => {
}).toThrowErrorMatchingSnapshot();
});
it('throws error for required fields', () => {
expect(() =>
validateConfig(
{
invalidField: true,
presets: {},
stylesheets: {},
themes: {},
scripts: {},
},
'docusaurus.config.js',
),
).toThrowErrorMatchingSnapshot();
});
});
describe('config warning and error', () => {
function getWarning(config: unknown) {
return ConfigSchema.validate(config).warning;
}
it('baseConfig has no warning', () => {
const warning = getWarning(baseConfig);
expect(warning).toBeUndefined();
});
});
describe('url', () => {
it('throws for non-string URLs', () => {
expect(() =>
normalizeConfig({
@ -180,195 +208,19 @@ describe('normalizeConfig', () => {
).toBe('/noSlash/foo/');
});
it.each([
['should throw error if plugins is not array', {}],
[
"should throw error if plugins is not a string and it's not an array #1",
[123],
],
[
'should throw error if plugins is not an array of [string, object][] #1',
[['example/path', 'wrong parameter here']],
],
[
'should throw error if plugins is not an array of [string, object][] #2',
[[{}, 'example/path']],
],
[
'should throw error if plugins is not an array of [string, object][] #3',
[[{}, {}]],
],
])(`%s for the input of: %p`, (_message, plugins) => {
expect(() => {
normalizeConfig({
// @ts-expect-error: test
plugins,
});
}).toThrowErrorMatchingSnapshot();
});
it.each([
['should throw error if themes is not array', {}],
[
"should throw error if themes is not a string and it's not an array #1",
[123],
],
[
'should throw error if themes is not an array of [string, object][] #1',
[['example/path', 'wrong parameter here']],
],
[
'should throw error if themes is not an array of [string, object][] #2',
[[{}, 'example/path']],
],
[
'should throw error if themes is not an array of [string, object][] #3',
[[{}, {}]],
],
])(`%s for the input of: %p`, (_message, themes) => {
expect(() => {
normalizeConfig({
// @ts-expect-error: test
themes,
});
}).toThrowErrorMatchingSnapshot();
});
it.each([
['should accept [string] for plugins', ['plain/string']],
[
'should accept string[] for plugins',
['plain/string', 'another/plain/string/path'],
],
[
'should accept [string, object] for plugins',
[['plain/string', {it: 'should work'}]],
],
[
'should accept [string, object][] for plugins',
[
['plain/string', {it: 'should work'}],
['this/should/work', {too: 'yes'}],
],
],
[
'should accept ([string, object]|string)[] for plugins',
[
'plain/string',
['plain', {it: 'should work'}],
['this/should/work', {too: 'yes'}],
],
],
['should accept function for plugin', [function plugin() {}]],
[
'should accept [function, object] for plugin',
[[() => {}, {it: 'should work'}]],
],
['should accept false/null for plugin', [false as const, null, 'classic']],
])(`%s for the input of: %p`, (_message, plugins) => {
expect(() => {
normalizeConfig({
plugins,
} as Config);
}).not.toThrow();
});
it.each([
['should accept [string] for themes', ['plain/string']],
[
'should accept string[] for themes',
['plain/string', 'another/plain/string/path'],
],
[
'should accept [string, object] for themes',
[['plain/string', {it: 'should work'}]],
],
[
'should accept [string, object][] for themes',
[
['plain/string', {it: 'should work'}],
['this/should/work', {too: 'yes'}],
],
],
[
'should accept ([string, object]|string)[] for themes',
[
'plain/string',
['plain', {it: 'should work'}],
['this/should/work', {too: 'yes'}],
],
],
['should accept function for theme', [function theme() {}]],
[
'should accept [function, object] for theme',
[[function theme() {}, {it: 'should work'}]],
],
['should accept false/null for themes', [false, null, 'classic']],
])(`%s for the input of: %p`, (_message, themes) => {
expect(() => {
normalizeConfig({
themes,
} as Config);
}).not.toThrow();
});
it('throws error if themes is not array', () => {
expect(() => {
normalizeConfig({
// @ts-expect-error: test
themes: {},
});
}).toThrowErrorMatchingInlineSnapshot(`
""themes" must be an array
"
`);
});
it('throws error if presets is not array', () => {
expect(() => {
normalizeConfig({
// @ts-expect-error: test
presets: {},
});
}).toThrowErrorMatchingInlineSnapshot(`
""presets" must be an array
"
`);
});
it('throws error if presets looks invalid', () => {
expect(() => {
normalizeConfig({
// @ts-expect-error: test
presets: [() => {}],
});
}).toThrowErrorMatchingInlineSnapshot(`
""presets[0]" does not look like a valid preset config. A preset config entry should be one of:
- A tuple of [presetName, options], like \`["classic", { blog: false }]\`, or
- A simple string, like \`"classic"\`
"
`);
});
it('accepts presets as false / null', () => {
expect(() => {
normalizeConfig({
presets: [false, null, 'classic'],
});
}).not.toThrow();
});
it("throws error if scripts doesn't have src", () => {
expect(() => {
normalizeConfig({
scripts: ['https://some.com', {}],
});
}).toThrowErrorMatchingInlineSnapshot(`
""scripts[1]" is invalid. A script must be a plain string (the src), or an object with at least a "src" property.
"
`);
it('site url fails validation when using subpath', () => {
const {error} = ConfigSchema.validate({
...baseConfig,
url: 'https://mysite.com/someSubpath',
});
expect(error).toBeDefined();
expect(error?.message).toBe(
'The url is not supposed to contain a sub-path like "/someSubpath". Please use the baseUrl field for sub-paths.',
);
});
});
describe('headTags', () => {
it('accepts headTags with tagName and attributes', () => {
expect(() => {
normalizeConfig({
@ -436,7 +288,9 @@ describe('normalizeConfig', () => {
"
`);
});
});
describe('css', () => {
it("throws error if css doesn't have href", () => {
expect(() => {
normalizeConfig({
@ -447,22 +301,22 @@ describe('normalizeConfig', () => {
"
`);
});
});
it('throws error for required fields', () => {
expect(() =>
validateConfig(
{
invalidField: true,
presets: {},
stylesheets: {},
themes: {},
scripts: {},
},
'docusaurus.config.js',
),
).toThrowErrorMatchingSnapshot();
describe('scripts', () => {
it("throws error if scripts doesn't have src", () => {
expect(() => {
normalizeConfig({
scripts: ['https://some.com', {}],
});
}).toThrowErrorMatchingInlineSnapshot(`
""scripts[1]" is invalid. A script must be a plain string (the src), or an object with at least a "src" property.
"
`);
});
});
describe('onBrokenLinks', () => {
it('throws for "error" reporting severity', () => {
expect(() =>
validateConfig(
@ -478,28 +332,6 @@ describe('normalizeConfig', () => {
});
});
describe('config warning and error', () => {
function getWarning(config: unknown) {
return ConfigSchema.validate(config).warning;
}
it('baseConfig has no warning', () => {
const warning = getWarning(baseConfig);
expect(warning).toBeUndefined();
});
it('site url fails validation when using subpath', () => {
const {error} = ConfigSchema.validate({
...baseConfig,
url: 'https://mysite.com/someSubpath',
});
expect(error).toBeDefined();
expect(error?.message).toBe(
'The url is not supposed to contain a sub-path like "/someSubpath". Please use the baseUrl field for sub-paths.',
);
});
});
describe('markdown', () => {
it('accepts undefined object', () => {
expect(
@ -597,8 +429,14 @@ describe('markdown', () => {
});
it('throw for bad markdown format', () => {
expect(() => normalizeConfig({markdown: {format: null}}))
.toThrowErrorMatchingInlineSnapshot(`
expect(() =>
normalizeConfig({
markdown: {
// @ts-expect-error: bad value
format: null,
},
}),
).toThrowErrorMatchingInlineSnapshot(`
""markdown.format" must be one of [mdx, md, detect]
"markdown.format" must be a string
"
@ -617,6 +455,7 @@ describe('markdown', () => {
it('throw for null object', () => {
expect(() => {
normalizeConfig({
// @ts-expect-error: bad value
markdown: null,
});
}).toThrowErrorMatchingInlineSnapshot(`
@ -626,6 +465,245 @@ describe('markdown', () => {
});
});
describe('plugins', () => {
// Only here to verify typing
function ensurePlugins(plugins: PluginConfig[]): PluginConfig[] {
return plugins;
}
it.each([
['should throw error if plugins is not array', {}],
[
"should throw error if plugins is not a string and it's not an array #1",
[123],
],
[
'should throw error if plugins is not an array of [string, object][] #1',
[['example/path', 'wrong parameter here']],
],
[
'should throw error if plugins is not an array of [string, object][] #2',
[[{}, 'example/path']],
],
[
'should throw error if plugins is not an array of [string, object][] #3',
[[{}, {}]],
],
])(`%s for the input of: %p`, (_message, plugins) => {
expect(() => {
normalizeConfig({
// @ts-expect-error: test
plugins,
});
}).toThrowErrorMatchingSnapshot();
});
it.each([
['should accept [string] for plugins', ensurePlugins(['plain/string'])],
[
'should accept string[] for plugins',
ensurePlugins(['plain/string', 'another/plain/string/path']),
],
[
'should accept [string, object] for plugins',
ensurePlugins([['plain/string', {it: 'should work'}]]),
],
[
'should accept [string, object][] for plugins',
ensurePlugins([
['plain/string', {it: 'should work'}],
['this/should/work', {too: 'yes'}],
]),
],
[
'should accept ([string, object]|string)[] for plugins',
ensurePlugins([
'plain/string',
['plain', {it: 'should work'}],
['this/should/work', {too: 'yes'}],
]),
],
[
'should accept function returning null',
ensurePlugins([
function plugin() {
return null;
},
]),
],
[
'should accept function returning plugin',
ensurePlugins([
function plugin() {
return {name: 'plugin'};
},
]),
],
[
'should accept function returning plugin or null',
ensurePlugins([
function plugin() {
return Math.random() > 0.5 ? null : {name: 'plugin'};
},
]),
],
[
'should accept async function returning null',
ensurePlugins([
async function plugin() {
return null;
},
]),
],
[
'should accept async function returning plugin',
ensurePlugins([
async function plugin() {
return {name: 'plugin'};
},
]),
],
[
'should accept function returning plugin or null',
ensurePlugins([
async function plugin() {
return Math.random() > 0.5 ? null : {name: 'plugin'};
},
]),
],
[
'should accept [function, object] for plugin',
[[() => {}, {it: 'should work'}]],
],
[
'should accept false/null for plugin',
ensurePlugins([false as const, null, 'classic']),
],
])(`%s for the input of: %p`, (_message, plugins) => {
expect(() => {
normalizeConfig({
plugins,
} as Config);
}).not.toThrow();
});
});
describe('themes', () => {
it.each([
['should throw error if themes is not array', {}],
[
"should throw error if themes is not a string and it's not an array #1",
[123],
],
[
'should throw error if themes is not an array of [string, object][] #1',
[['example/path', 'wrong parameter here']],
],
[
'should throw error if themes is not an array of [string, object][] #2',
[[{}, 'example/path']],
],
[
'should throw error if themes is not an array of [string, object][] #3',
[[{}, {}]],
],
])(`%s for the input of: %p`, (_message, themes) => {
expect(() => {
normalizeConfig({
// @ts-expect-error: test
themes,
});
}).toThrowErrorMatchingSnapshot();
});
it.each([
['should accept [string] for themes', ['plain/string']],
[
'should accept string[] for themes',
['plain/string', 'another/plain/string/path'],
],
[
'should accept [string, object] for themes',
[['plain/string', {it: 'should work'}]],
],
[
'should accept [string, object][] for themes',
[
['plain/string', {it: 'should work'}],
['this/should/work', {too: 'yes'}],
],
],
[
'should accept ([string, object]|string)[] for themes',
[
'plain/string',
['plain', {it: 'should work'}],
['this/should/work', {too: 'yes'}],
],
],
['should accept function for theme', [function theme() {}]],
[
'should accept [function, object] for theme',
[[function theme() {}, {it: 'should work'}]],
],
['should accept false/null for themes', [false, null, 'classic']],
])(`%s for the input of: %p`, (_message, themes) => {
expect(() => {
normalizeConfig({
themes,
} as Config);
}).not.toThrow();
});
it('throws error if themes is not array', () => {
expect(() => {
normalizeConfig({
// @ts-expect-error: test
themes: {},
});
}).toThrowErrorMatchingInlineSnapshot(`
""themes" must be an array
"
`);
});
});
describe('presets', () => {
it('throws error if presets is not array', () => {
expect(() => {
normalizeConfig({
// @ts-expect-error: test
presets: {},
});
}).toThrowErrorMatchingInlineSnapshot(`
""presets" must be an array
"
`);
});
it('throws error if presets looks invalid', () => {
expect(() => {
normalizeConfig({
// @ts-expect-error: test
presets: [() => {}],
});
}).toThrowErrorMatchingInlineSnapshot(`
""presets[0]" does not look like a valid preset config. A preset config entry should be one of:
- A tuple of [presetName, options], like \`["classic", { blog: false }]\`, or
- A simple string, like \`"classic"\`
"
`);
});
it('accepts presets as false / null', () => {
expect(() => {
normalizeConfig({
presets: [false, null, 'classic'],
});
}).not.toThrow();
});
});
describe('future', () => {
it('accepts future - undefined', () => {
expect(
@ -737,6 +815,7 @@ describe('future', () => {
});
it('rejects router - null', () => {
// @ts-expect-error: bad value
const router: DocusaurusConfig['future']['experimental_router'] = null;
expect(() =>
normalizeConfig({
@ -976,6 +1055,7 @@ describe('future', () => {
});
it('rejects namespace - null', () => {
// @ts-expect-error: bad value
const storage: Partial<StorageConfig> = {namespace: null};
expect(() =>
normalizeConfig({