mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-24 05:28:01 +02:00
feat(docs, blog): add support for tags.yml
, predefined list of tags (#10137)
Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com> Co-authored-by: OzakIOne <OzakIOne@users.noreply.github.com> Co-authored-by: sebastien <lorber.sebastien@gmail.com> Co-authored-by: slorber <slorber@users.noreply.github.com>
This commit is contained in:
parent
1049294ba6
commit
0eb7b64aac
63 changed files with 2597 additions and 722 deletions
|
@ -0,0 +1,538 @@
|
|||
/**
|
||||
* 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 * as path from 'path';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as tmp from 'tmp-promise';
|
||||
import * as YAML from 'js-yaml';
|
||||
import {
|
||||
ensureUniquePermalinks,
|
||||
getTagsFile,
|
||||
getTagsFilePathsToWatch,
|
||||
normalizeTagsFile,
|
||||
} from '../tagsFile';
|
||||
import type {TagsFile, TagsFileInput} from '@docusaurus/utils';
|
||||
|
||||
describe('ensureUniquePermalinks', () => {
|
||||
it('throw when one duplicate permalink found', () => {
|
||||
const definedTags: TagsFile = {
|
||||
open: {
|
||||
label: 'Open Source',
|
||||
permalink: '/custom-open-source',
|
||||
description: 'Learn about the open source',
|
||||
},
|
||||
closed: {
|
||||
label: 'Closed Source',
|
||||
permalink: '/custom-open-source',
|
||||
description: 'Learn about the closed source',
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => ensureUniquePermalinks(definedTags))
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Duplicate permalinks found in tags file:
|
||||
- /custom-open-source"
|
||||
`);
|
||||
});
|
||||
|
||||
it('throw when multiple duplicate permalink found', () => {
|
||||
const definedTags: TagsFile = {
|
||||
open: {
|
||||
label: 'Open Source',
|
||||
permalink: '/custom-open-source',
|
||||
description: 'Learn about the open source',
|
||||
},
|
||||
closed: {
|
||||
label: 'Closed Source',
|
||||
permalink: '/custom-open-source',
|
||||
description: 'Learn about the closed source',
|
||||
},
|
||||
hello: {
|
||||
label: 'Hello',
|
||||
permalink: '/hello',
|
||||
description: 'Learn about the hello',
|
||||
},
|
||||
world: {
|
||||
label: 'Hello',
|
||||
permalink: '/hello',
|
||||
description: 'Learn about the world',
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => ensureUniquePermalinks(definedTags))
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Duplicate permalinks found in tags file:
|
||||
- /custom-open-source
|
||||
- /hello"
|
||||
`);
|
||||
});
|
||||
|
||||
it('do not throw when no duplicate permalink found', () => {
|
||||
const definedTags: TagsFile = {
|
||||
open: {
|
||||
label: 'Open Source',
|
||||
permalink: '/open-source',
|
||||
description: 'Learn about the open source',
|
||||
},
|
||||
closed: {
|
||||
label: 'Closed Source',
|
||||
permalink: '/closed-source',
|
||||
description: 'Learn about the closed source',
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => ensureUniquePermalinks(definedTags)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeTagsFile', () => {
|
||||
it('normalize null tag', () => {
|
||||
const input: TagsFileInput = {
|
||||
'kebab case test': null,
|
||||
};
|
||||
|
||||
const expectedOutput: TagsFile = {
|
||||
'kebab case test': {
|
||||
description: undefined,
|
||||
label: 'Kebab case test',
|
||||
permalink: '/kebab-case-test',
|
||||
},
|
||||
};
|
||||
|
||||
expect(normalizeTagsFile(input)).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it('normalize partial tag with label', () => {
|
||||
const input: TagsFileInput = {
|
||||
world: {label: 'WORLD'},
|
||||
};
|
||||
|
||||
const expectedOutput: TagsFile = {
|
||||
world: {
|
||||
description: undefined,
|
||||
label: 'WORLD',
|
||||
permalink: '/world',
|
||||
},
|
||||
};
|
||||
|
||||
expect(normalizeTagsFile(input)).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it('normalize partial tag with description', () => {
|
||||
const input: TagsFileInput = {
|
||||
world: {description: 'World description test'},
|
||||
};
|
||||
|
||||
const expectedOutput: TagsFile = {
|
||||
world: {
|
||||
description: 'World description test',
|
||||
label: 'World',
|
||||
permalink: '/world',
|
||||
},
|
||||
};
|
||||
|
||||
expect(normalizeTagsFile(input)).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it('normalize partial tag with permalink', () => {
|
||||
const input: TagsFileInput = {
|
||||
world: {permalink: 'world'},
|
||||
};
|
||||
|
||||
const expectedOutput: TagsFile = {
|
||||
world: {
|
||||
description: undefined,
|
||||
label: 'World',
|
||||
permalink: 'world',
|
||||
},
|
||||
};
|
||||
|
||||
expect(normalizeTagsFile(input)).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it('does not modify fully defined tags', () => {
|
||||
const input: TagsFileInput = {
|
||||
tag1: {
|
||||
label: 'Custom Label',
|
||||
description: 'Custom Description',
|
||||
permalink: 'custom-permalink',
|
||||
},
|
||||
};
|
||||
|
||||
expect(normalizeTagsFile(input)).toEqual(input);
|
||||
});
|
||||
|
||||
it('handle special characters in keys', () => {
|
||||
const input: TagsFileInput = {
|
||||
'special@char$!key': null,
|
||||
};
|
||||
|
||||
const expectedOutput: TagsFile = {
|
||||
'special@char$!key': {
|
||||
description: undefined,
|
||||
label: 'Special@char$!key',
|
||||
permalink: '/special-char-key',
|
||||
},
|
||||
};
|
||||
|
||||
expect(normalizeTagsFile(input)).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it('handle special characters in keys with chinese characters', () => {
|
||||
const input: TagsFileInput = {
|
||||
特殊字符测试: null,
|
||||
};
|
||||
|
||||
const expectedOutput: TagsFile = {
|
||||
特殊字符测试: {
|
||||
description: undefined,
|
||||
label: '特殊字符测试',
|
||||
permalink: '/特殊字符测试',
|
||||
},
|
||||
};
|
||||
|
||||
expect(normalizeTagsFile(input)).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
it('normalize test', () => {
|
||||
const input: TagsFileInput = {
|
||||
world: {permalink: 'aze'},
|
||||
hello: {permalink: 'h e l l o'},
|
||||
};
|
||||
|
||||
const expectedOutput = {
|
||||
world: {
|
||||
description: undefined,
|
||||
label: 'World',
|
||||
permalink: 'aze',
|
||||
},
|
||||
hello: {
|
||||
description: undefined,
|
||||
label: 'Hello',
|
||||
permalink: 'h e l l o',
|
||||
},
|
||||
};
|
||||
|
||||
expect(normalizeTagsFile(input)).toEqual(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTagsFilePathsToWatch', () => {
|
||||
it('returns tags file paths - tags undefined', () => {
|
||||
expect(
|
||||
getTagsFilePathsToWatch({
|
||||
tags: undefined,
|
||||
contentPaths: {
|
||||
contentPath: '/user/blog',
|
||||
contentPathLocalized: '/i18n/blog',
|
||||
},
|
||||
}),
|
||||
).toEqual(['/i18n/blog/tags.yml', '/user/blog/tags.yml']);
|
||||
});
|
||||
|
||||
it('returns tags file paths - tags.yml', () => {
|
||||
expect(
|
||||
getTagsFilePathsToWatch({
|
||||
tags: 'tags.yml',
|
||||
contentPaths: {
|
||||
contentPath: '/user/blog',
|
||||
contentPathLocalized: '/i18n/blog',
|
||||
},
|
||||
}),
|
||||
).toEqual(['/i18n/blog/tags.yml', '/user/blog/tags.yml']);
|
||||
});
|
||||
|
||||
it('returns tags file paths - customTags.yml', () => {
|
||||
expect(
|
||||
getTagsFilePathsToWatch({
|
||||
tags: 'customTags.yml',
|
||||
contentPaths: {
|
||||
contentPath: '/user/blog',
|
||||
contentPathLocalized: '/i18n/blog',
|
||||
},
|
||||
}),
|
||||
).toEqual(['/i18n/blog/customTags.yml', '/user/blog/customTags.yml']);
|
||||
});
|
||||
|
||||
it('returns [] - tags: null', () => {
|
||||
expect(
|
||||
getTagsFilePathsToWatch({
|
||||
tags: null,
|
||||
contentPaths: {
|
||||
contentPath: '/user/blog',
|
||||
contentPathLocalized: '/i18n/blog',
|
||||
},
|
||||
}),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns [] - tags: false', () => {
|
||||
expect(
|
||||
getTagsFilePathsToWatch({
|
||||
tags: false,
|
||||
contentPaths: {
|
||||
contentPath: '/user/blog',
|
||||
contentPathLocalized: '/i18n/blog',
|
||||
},
|
||||
}),
|
||||
).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTagsFile', () => {
|
||||
async function createTestTagsFile({
|
||||
filePath,
|
||||
tagsFileInput,
|
||||
}: {
|
||||
filePath: string;
|
||||
tagsFileInput: TagsFileInput;
|
||||
}): Promise<{dir: string}> {
|
||||
async function createTmpDir() {
|
||||
return (
|
||||
await tmp.dir({
|
||||
prefix: 'jest-createTmpSiteDir',
|
||||
})
|
||||
).path;
|
||||
}
|
||||
const contentPath = await createTmpDir();
|
||||
const finalFilePath = path.join(contentPath, filePath);
|
||||
const fileContent = YAML.dump(tagsFileInput);
|
||||
await fs.writeFile(finalFilePath, fileContent);
|
||||
return {dir: contentPath};
|
||||
}
|
||||
|
||||
type Params = Parameters<typeof getTagsFile>[0];
|
||||
|
||||
it('reads tags file - regular', async () => {
|
||||
const {dir} = await createTestTagsFile({
|
||||
filePath: 'tags.yml',
|
||||
tagsFileInput: {
|
||||
tag1: {label: 'Tag1 Label'},
|
||||
tag2: {description: 'Tag2 Description'},
|
||||
tag3: {
|
||||
label: 'Tag3 Label',
|
||||
permalink: '/tag-3',
|
||||
description: 'Tag3 Description',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const params: Params = {
|
||||
contentPaths: {contentPath: dir, contentPathLocalized: dir},
|
||||
tags: 'tags.yml',
|
||||
};
|
||||
|
||||
await expect(getTagsFile(params)).resolves.toMatchInlineSnapshot(`
|
||||
{
|
||||
"tag1": {
|
||||
"description": undefined,
|
||||
"label": "Tag1 Label",
|
||||
"permalink": "/tag-1",
|
||||
},
|
||||
"tag2": {
|
||||
"description": "Tag2 Description",
|
||||
"label": "Tag2",
|
||||
"permalink": "/tag-2",
|
||||
},
|
||||
"tag3": {
|
||||
"description": "Tag3 Description",
|
||||
"label": "Tag3 Label",
|
||||
"permalink": "/tag-3",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('reads tags file - only keys', async () => {
|
||||
const {dir} = await createTestTagsFile({
|
||||
filePath: 'tags.yml',
|
||||
tagsFileInput: {
|
||||
tagKey: null,
|
||||
},
|
||||
});
|
||||
|
||||
const params: Params = {
|
||||
contentPaths: {contentPath: dir, contentPathLocalized: dir},
|
||||
tags: 'tags.yml',
|
||||
};
|
||||
|
||||
await expect(getTagsFile(params)).resolves.toMatchInlineSnapshot(`
|
||||
{
|
||||
"tagKey": {
|
||||
"description": undefined,
|
||||
"label": "Tagkey",
|
||||
"permalink": "/tag-key",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('reads tags file - tags option undefined', async () => {
|
||||
const {dir} = await createTestTagsFile({
|
||||
filePath: 'tags.yml',
|
||||
tagsFileInput: {
|
||||
tag: {label: 'tag label'},
|
||||
},
|
||||
});
|
||||
|
||||
const params: Params = {
|
||||
contentPaths: {contentPath: dir, contentPathLocalized: dir},
|
||||
tags: undefined,
|
||||
};
|
||||
|
||||
await expect(getTagsFile(params)).resolves.toMatchInlineSnapshot(`
|
||||
{
|
||||
"tag": {
|
||||
"description": undefined,
|
||||
"label": "tag label",
|
||||
"permalink": "/tag",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('reads tags file - empty file', async () => {
|
||||
const {dir} = await createTestTagsFile({
|
||||
filePath: 'tags.yml',
|
||||
tagsFileInput: {},
|
||||
});
|
||||
|
||||
const params: Params = {
|
||||
contentPaths: {contentPath: dir, contentPathLocalized: dir},
|
||||
tags: undefined,
|
||||
};
|
||||
|
||||
await expect(getTagsFile(params)).resolves.toEqual({});
|
||||
});
|
||||
|
||||
it('reads tags file - prioritizes reading from localized content path', async () => {
|
||||
const {dir} = await createTestTagsFile({
|
||||
filePath: 'tags.yml',
|
||||
tagsFileInput: {
|
||||
tag: {label: 'tag label'},
|
||||
},
|
||||
});
|
||||
|
||||
const {dir: dirLocalized} = await createTestTagsFile({
|
||||
filePath: 'tags.yml',
|
||||
tagsFileInput: {
|
||||
tag: {label: 'tag label (localized)'},
|
||||
},
|
||||
});
|
||||
|
||||
const params: Params = {
|
||||
contentPaths: {contentPath: dir, contentPathLocalized: dirLocalized},
|
||||
tags: undefined,
|
||||
};
|
||||
|
||||
await expect(getTagsFile(params)).resolves.toMatchInlineSnapshot(`
|
||||
{
|
||||
"tag": {
|
||||
"description": undefined,
|
||||
"label": "tag label (localized)",
|
||||
"permalink": "/tag",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('reads tags file - custom tags file path', async () => {
|
||||
const {dir} = await createTestTagsFile({
|
||||
filePath: 'custom-tags-path.yml',
|
||||
tagsFileInput: {
|
||||
tag: {label: 'tag label'},
|
||||
},
|
||||
});
|
||||
|
||||
const params: Params = {
|
||||
contentPaths: {contentPath: dir, contentPathLocalized: dir},
|
||||
tags: 'custom-tags-path.yml',
|
||||
};
|
||||
|
||||
await expect(getTagsFile(params)).resolves.toMatchInlineSnapshot(`
|
||||
{
|
||||
"tag": {
|
||||
"description": undefined,
|
||||
"label": "tag label",
|
||||
"permalink": "/tag",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('throws if duplicate permalink', async () => {
|
||||
const {dir} = await createTestTagsFile({
|
||||
filePath: 'tags.yml',
|
||||
tagsFileInput: {
|
||||
tag1: {permalink: '/duplicate'},
|
||||
tag2: {permalink: '/duplicate'},
|
||||
},
|
||||
});
|
||||
|
||||
const params: Params = {
|
||||
contentPaths: {contentPath: dir, contentPathLocalized: dir},
|
||||
tags: undefined,
|
||||
};
|
||||
|
||||
await expect(getTagsFile(params)).rejects.toMatchInlineSnapshot(`
|
||||
[Error: Duplicate permalinks found in tags file:
|
||||
- /duplicate]
|
||||
`);
|
||||
});
|
||||
|
||||
it('throws if custom tags file path does not exist', async () => {
|
||||
const params: Params = {
|
||||
contentPaths: {contentPath: 'any', contentPathLocalized: 'localizedAny'},
|
||||
tags: 'custom-tags-path.yml',
|
||||
};
|
||||
|
||||
await expect(getTagsFile(params)).rejects
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"No tags file 'custom-tags-path.yml' could be found in any of those directories:
|
||||
- localizedAny
|
||||
- any"
|
||||
`);
|
||||
});
|
||||
|
||||
it('does not read tags file - tags option null/false', async () => {
|
||||
const {dir} = await createTestTagsFile({
|
||||
filePath: 'tags.yml',
|
||||
tagsFileInput: {
|
||||
tag: {label: 'tag label'},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
getTagsFile({
|
||||
contentPaths: {contentPath: dir, contentPathLocalized: dir},
|
||||
tags: null,
|
||||
}),
|
||||
).resolves.toBeNull();
|
||||
await expect(
|
||||
getTagsFile({
|
||||
contentPaths: {contentPath: dir, contentPathLocalized: dir},
|
||||
tags: false,
|
||||
}),
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it('does not read tags file - tags files has non-default name', async () => {
|
||||
const {dir} = await createTestTagsFile({
|
||||
filePath: 'bad-tags-file-name.yml',
|
||||
tagsFileInput: {
|
||||
tag: {label: 'tag label'},
|
||||
},
|
||||
});
|
||||
|
||||
const params: Params = {
|
||||
contentPaths: {contentPath: dir, contentPathLocalized: dir},
|
||||
tags: undefined,
|
||||
};
|
||||
|
||||
await expect(getTagsFile(params)).resolves.toBeNull();
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue