From bed9bb306329ffa7e04eb480a489fbad801e1e12 Mon Sep 17 00:00:00 2001 From: ozakione <29860391+OzakIOne@users.noreply.github.com> Date: Fri, 29 Mar 2024 14:49:55 +0100 Subject: [PATCH] wip tests --- .../.npmignore | 0 .../README.md | 7 + .../package.json | 4 +- .../__fixtures__/website/docusaurus.config.js | 21 + .../website/src/showcase/site.yml | 6 + .../__snapshots__/index.test.ts.snap | 19 + .../src/__tests__/frontMatter.test.ts | 479 ++++++++++++++++++ .../src/__tests__/index.test.ts | 32 ++ .../src/__tests__/options.test.ts | 63 +++ .../src/index.ts | 123 +++++ .../src/markdownLoader.ts | 0 .../src/options.ts | 29 +- .../src/plugin-content-showcase.d.ts} | 5 +- .../src/types.ts | 0 .../src/yaml.ts | 24 + .../tsconfig.json | 0 packages/docusaurus-plugin-showcase/README.md | 7 - .../docusaurus-plugin-showcase/src/index.ts | 257 ---------- .../docusaurus-theme-classic/package.json | 2 +- .../src/theme-classic.d.ts | 4 +- .../src/theme/Showcase/index.tsx | 2 +- 21 files changed, 785 insertions(+), 299 deletions(-) rename packages/{docusaurus-plugin-showcase => docusaurus-plugin-content-showcase}/.npmignore (100%) create mode 100644 packages/docusaurus-plugin-content-showcase/README.md rename packages/{docusaurus-plugin-showcase => docusaurus-plugin-content-showcase}/package.json (87%) create mode 100644 packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/docusaurus.config.js create mode 100644 packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/src/showcase/site.yml create mode 100644 packages/docusaurus-plugin-content-showcase/src/__tests__/__snapshots__/index.test.ts.snap create mode 100644 packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts create mode 100644 packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts create mode 100644 packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts create mode 100644 packages/docusaurus-plugin-content-showcase/src/index.ts rename packages/{docusaurus-plugin-showcase => docusaurus-plugin-content-showcase}/src/markdownLoader.ts (100%) rename packages/{docusaurus-plugin-showcase => docusaurus-plugin-content-showcase}/src/options.ts (61%) rename packages/{docusaurus-plugin-showcase/src/plugin-showcase.d.ts => docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts} (94%) rename packages/{docusaurus-plugin-showcase => docusaurus-plugin-content-showcase}/src/types.ts (100%) create mode 100644 packages/docusaurus-plugin-content-showcase/src/yaml.ts rename packages/{docusaurus-plugin-showcase => docusaurus-plugin-content-showcase}/tsconfig.json (100%) delete mode 100644 packages/docusaurus-plugin-showcase/README.md delete mode 100644 packages/docusaurus-plugin-showcase/src/index.ts diff --git a/packages/docusaurus-plugin-showcase/.npmignore b/packages/docusaurus-plugin-content-showcase/.npmignore similarity index 100% rename from packages/docusaurus-plugin-showcase/.npmignore rename to packages/docusaurus-plugin-content-showcase/.npmignore diff --git a/packages/docusaurus-plugin-content-showcase/README.md b/packages/docusaurus-plugin-content-showcase/README.md new file mode 100644 index 0000000000..33623bba6c --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/README.md @@ -0,0 +1,7 @@ +# `@docusaurus/plugin-content-showcase` + +Showcase plugin for Docusaurus. + +## Usage + +See [plugin-content-showcase documentation](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-showcase). diff --git a/packages/docusaurus-plugin-showcase/package.json b/packages/docusaurus-plugin-content-showcase/package.json similarity index 87% rename from packages/docusaurus-plugin-showcase/package.json rename to packages/docusaurus-plugin-content-showcase/package.json index b5c3f6988a..16cea13dd5 100644 --- a/packages/docusaurus-plugin-showcase/package.json +++ b/packages/docusaurus-plugin-content-showcase/package.json @@ -3,7 +3,7 @@ "version": "3.0.0", "description": "Showcase plugin for Docusaurus.", "main": "lib/index.js", - "types": "src/plugin-showcase.d.ts", + "types": "src/plugin-content-showcase.d.ts", "scripts": { "build": "tsc", "watch": "tsc --watch" @@ -14,7 +14,7 @@ "repository": { "type": "git", "url": "https://github.com/facebook/docusaurus.git", - "directory": "packages/docusaurus-plugin-showcase" + "directory": "packages/docusaurus-plugin-content-showcase" }, "license": "MIT", "dependencies": { diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/docusaurus.config.js b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/docusaurus.config.js new file mode 100644 index 0000000000..d048d2caf5 --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/docusaurus.config.js @@ -0,0 +1,21 @@ +/** + * 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. + */ + +module.exports = { + title: 'My Site', + tagline: 'The tagline of my site', + url: 'https://your-docusaurus-site.example.com', + baseUrl: '/', + favicon: 'img/favicon.ico', + markdown: { + parseFrontMatter: async (params) => { + const result = await params.defaultParseFrontMatter(params); + result.frontMatter.custom_frontMatter = 'added by parseFrontMatter'; + return result; + }, + }, +}; diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/src/showcase/site.yml b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/src/showcase/site.yml new file mode 100644 index 0000000000..fcd8f830b8 --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/__fixtures__/website/src/showcase/site.yml @@ -0,0 +1,6 @@ +title: "Hello" +description: "World" +preview: github.com/ozakione.png +website: "https://docusaurus.io/" +source: "https://github.com/facebook/docusaurus" +tags: ["opensource", "meta"] diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-showcase/src/__tests__/__snapshots__/index.test.ts.snap new file mode 100644 index 0000000000..f23e1c240f --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/__snapshots__/index.test.ts.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`docusaurus-plugin-content-showcase loads simple showcase 1`] = ` +{ + "website": [ + { + "description": "World", + "preview": "github.com/ozakione.png", + "source": "https://github.com/facebook/docusaurus", + "tags": [ + "opensource", + "meta", + ], + "title": "Hello", + "website": "https://docusaurus.io/", + }, + ], +} +`; diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts new file mode 100644 index 0000000000..6a2f7b0e4a --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/frontMatter.test.ts @@ -0,0 +1,479 @@ +/** + * 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 {escapeRegexp} from '@docusaurus/utils'; +import {validateShowcaseFrontMatter} from '../yaml'; +import type {ShowcaseFrontMatter} from '@docusaurus/plugin-content-showcase'; + +function testField(params: { + prefix: string; + validFrontMatters: ShowcaseFrontMatter[]; + convertibleFrontMatter?: [ + ConvertibleFrontMatter: {[key: string]: unknown}, + ConvertedFrontMatter: ShowcaseFrontMatter, + ][]; + invalidFrontMatters?: [ + InvalidFrontMatter: {[key: string]: unknown}, + ErrorMessage: string, + ][]; +}) { + // eslint-disable-next-line jest/require-top-level-describe + test(`[${params.prefix}] accept valid values`, () => { + params.validFrontMatters.forEach((frontMatter) => { + expect(validateShowcaseFrontMatter(frontMatter)).toEqual(frontMatter); + }); + }); + + // eslint-disable-next-line jest/require-top-level-describe + test(`[${params.prefix}] convert valid values`, () => { + params.convertibleFrontMatter?.forEach( + ([convertibleFrontMatter, convertedFrontMatter]) => { + expect(validateShowcaseFrontMatter(convertibleFrontMatter)).toEqual( + convertedFrontMatter, + ); + }, + ); + }); + + // eslint-disable-next-line jest/require-top-level-describe + test(`[${params.prefix}] throw error for values`, () => { + params.invalidFrontMatters?.forEach(([frontMatter, message]) => { + try { + validateShowcaseFrontMatter(frontMatter); + throw new Error( + `Doc front matter is expected to be rejected, but was accepted successfully:\n ${JSON.stringify( + frontMatter, + null, + 2, + )}`, + ); + } catch (err) { + // eslint-disable-next-line jest/no-conditional-expect + expect((err as Error).message).toMatch( + new RegExp(escapeRegexp(message)), + ); + } + }); + }); +} + +describe('doc front matter schema', () => { + it('accepts empty object', () => { + const frontMatter: ShowcaseFrontMatter = {}; + expect(validateShowcaseFrontMatter(frontMatter)).toEqual(frontMatter); + }); + + it('accepts unknown field', () => { + const frontMatter = {abc: '1'}; + expect(validateShowcaseFrontMatter(frontMatter)).toEqual(frontMatter); + }); +}); + +describe('validateShowcaseFrontMatter id', () => { + testField({ + prefix: 'id', + validFrontMatters: [{id: '123'}, {id: 'unique_id'}], + invalidFrontMatters: [[{id: ''}, 'is not allowed to be empty']], + }); +}); + +describe('validateShowcaseFrontMatter title', () => { + testField({ + prefix: 'title', + validFrontMatters: [ + // See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 + {title: ''}, + {title: 'title'}, + ], + }); +}); + +describe('validateShowcaseFrontMatter hide_title', () => { + testField({ + prefix: 'hide_title', + validFrontMatters: [{hide_title: true}, {hide_title: false}], + convertibleFrontMatter: [ + [{hide_title: 'true'}, {hide_title: true}], + [{hide_title: 'false'}, {hide_title: false}], + ], + invalidFrontMatters: [ + [{hide_title: 'yes'}, 'must be a boolean'], + [{hide_title: 'no'}, 'must be a boolean'], + [{hide_title: ''}, 'must be a boolean'], + ], + }); +}); + +describe('validateShowcaseFrontMatter hide_table_of_contents', () => { + testField({ + prefix: 'hide_table_of_contents', + validFrontMatters: [ + {hide_table_of_contents: true}, + {hide_table_of_contents: false}, + ], + convertibleFrontMatter: [ + [{hide_table_of_contents: 'true'}, {hide_table_of_contents: true}], + [{hide_table_of_contents: 'false'}, {hide_table_of_contents: false}], + ], + invalidFrontMatters: [ + [{hide_table_of_contents: 'yes'}, 'must be a boolean'], + [{hide_table_of_contents: 'no'}, 'must be a boolean'], + [{hide_table_of_contents: ''}, 'must be a boolean'], + ], + }); +}); + +describe('validateShowcaseFrontMatter keywords', () => { + testField({ + prefix: 'keywords', + validFrontMatters: [ + {keywords: ['hello']}, + {keywords: ['hello', 'world']}, + {keywords: ['hello', 'world']}, + {keywords: ['hello']}, + ], + invalidFrontMatters: [ + [{keywords: ''}, 'must be an array'], + [{keywords: ['']}, 'is not allowed to be empty'], + [{keywords: []}, 'does not contain 1 required value(s)'], + ], + }); +}); + +describe('validateShowcaseFrontMatter image', () => { + testField({ + prefix: 'image', + validFrontMatters: [ + {image: 'https://docusaurus.io/blog/image.png'}, + {image: '/absolute/image.png'}, + {image: '../relative/image.png'}, + ], + invalidFrontMatters: [ + [{image: ''}, '"image" does not look like a valid url (value=\'\')'], + ], + }); +}); + +describe('validateShowcaseFrontMatter description', () => { + testField({ + prefix: 'description', + validFrontMatters: [ + // See https://github.com/facebook/docusaurus/issues/4591#issuecomment-822372398 + {description: ''}, + {description: 'description'}, + ], + }); +}); + +describe('validateShowcaseFrontMatter slug', () => { + testField({ + prefix: 'slug', + validFrontMatters: [ + {slug: '/'}, + {slug: 'slug'}, + {slug: '/slug/'}, + {slug: './slug'}, + {slug: '../../slug'}, + {slug: '/api/plugins/@docusaurus'}, + {slug: '@site/api/asset'}, + {slug: 'slug1 slug2'}, + ], + invalidFrontMatters: [[{slug: ''}, 'is not allowed to be empty']], + }); +}); + +describe('validateShowcaseFrontMatter sidebar_label', () => { + testField({ + prefix: 'sidebar_label', + validFrontMatters: [{sidebar_label: 'Awesome docs'}], + invalidFrontMatters: [[{sidebar_label: ''}, 'is not allowed to be empty']], + }); +}); + +describe('validateShowcaseFrontMatter sidebar_position', () => { + testField({ + prefix: 'sidebar_position', + validFrontMatters: [ + {sidebar_position: -5}, + {sidebar_position: -3.5}, + {sidebar_position: 0}, + {sidebar_position: 5}, + {sidebar_position: 3.5}, + ], + convertibleFrontMatter: [ + [{sidebar_position: '-1.5'}, {sidebar_position: -1.5}], + [{sidebar_position: '1'}, {sidebar_position: 1}], + [{sidebar_position: '1.5'}, {sidebar_position: 1.5}], + ], + }); +}); + +describe('validateShowcaseFrontMatter sidebar_custom_props', () => { + testField({ + prefix: 'sidebar_custom_props', + validFrontMatters: [ + {sidebar_custom_props: {}}, + {sidebar_custom_props: {prop: 'custom', number: 1, boolean: true}}, + ], + invalidFrontMatters: [ + [{sidebar_custom_props: ''}, 'must be of type object'], + ], + }); +}); + +describe('validateShowcaseFrontMatter custom_edit_url', () => { + testField({ + prefix: 'custom_edit_url', + validFrontMatters: [ + // See https://github.com/demisto/content-docs/pull/616#issuecomment-827087566 + {custom_edit_url: ''}, + {custom_edit_url: null}, + {custom_edit_url: 'https://github.com/facebook/docusaurus/markdown.md'}, + {custom_edit_url: '../../api/docs/markdown.md'}, + {custom_edit_url: '@site/api/docs/markdown.md'}, + ], + }); +}); + +describe('validateShowcaseFrontMatter parse_number_prefixes', () => { + testField({ + prefix: 'parse_number_prefixes', + validFrontMatters: [ + {parse_number_prefixes: true}, + {parse_number_prefixes: false}, + ], + convertibleFrontMatter: [ + [{parse_number_prefixes: 'true'}, {parse_number_prefixes: true}], + [{parse_number_prefixes: 'false'}, {parse_number_prefixes: false}], + ], + invalidFrontMatters: [ + [{parse_number_prefixes: 'yes'}, 'must be a boolean'], + [{parse_number_prefixes: 'no'}, 'must be a boolean'], + [{parse_number_prefixes: ''}, 'must be a boolean'], + ], + }); +}); + +describe('validateShowcaseFrontMatter tags', () => { + testField({ + prefix: 'tags', + validFrontMatters: [{}, {tags: undefined}, {tags: ['tag1', 'tag2']}], + convertibleFrontMatter: [[{tags: ['tag1', 42]}, {tags: ['tag1', '42']}]], + invalidFrontMatters: [ + [ + {tags: 42}, + '"tags" does not look like a valid front matter Yaml array.', + ], + [ + {tags: 'tag1, tag2'}, + '"tags" does not look like a valid front matter Yaml array.', + ], + [{tags: [{}]}, '"tags[0]" does not look like a valid tag'], + [{tags: [true]}, '"tags[0]" does not look like a valid tag'], + [ + {tags: ['tag1', {hey: 'test'}]}, + '"tags[1]" does not look like a valid tag', + ], + ], + }); +}); + +describe('toc_min_heading_level', () => { + testField({ + prefix: 'toc_min_heading_level', + validFrontMatters: [ + {}, + {toc_min_heading_level: undefined}, + {toc_min_heading_level: 2}, + {toc_min_heading_level: 3}, + {toc_min_heading_level: 4}, + {toc_min_heading_level: 5}, + {toc_min_heading_level: 6}, + ], + convertibleFrontMatter: [ + [{toc_min_heading_level: '2'}, {toc_min_heading_level: 2}], + ], + invalidFrontMatters: [ + [ + {toc_min_heading_level: 1}, + '"toc_min_heading_level" must be greater than or equal to 2', + ], + [ + {toc_min_heading_level: 7}, + '"toc_min_heading_level" must be less than or equal to 6', + ], + [ + {toc_min_heading_level: 'hello'}, + '"toc_min_heading_level" must be a number', + ], + [ + {toc_min_heading_level: true}, + '"toc_min_heading_level" must be a number', + ], + ], + }); +}); + +describe('toc_max_heading_level', () => { + testField({ + prefix: 'toc_max_heading_level', + validFrontMatters: [ + {}, + {toc_max_heading_level: undefined}, + {toc_max_heading_level: 2}, + {toc_max_heading_level: 3}, + {toc_max_heading_level: 4}, + {toc_max_heading_level: 5}, + {toc_max_heading_level: 6}, + ], + convertibleFrontMatter: [ + [{toc_max_heading_level: '2'}, {toc_max_heading_level: 2}], + ], + invalidFrontMatters: [ + [ + {toc_max_heading_level: 1}, + '"toc_max_heading_level" must be greater than or equal to 2', + ], + [ + {toc_max_heading_level: 7}, + '"toc_max_heading_level" must be less than or equal to 6', + ], + [ + {toc_max_heading_level: 'hello'}, + '"toc_max_heading_level" must be a number', + ], + [ + {toc_max_heading_level: true}, + '"toc_max_heading_level" must be a number', + ], + ], + }); +}); + +describe('toc min/max consistency', () => { + testField({ + prefix: 'toc min/max', + validFrontMatters: [ + {}, + {toc_min_heading_level: undefined, toc_max_heading_level: undefined}, + {toc_min_heading_level: 2, toc_max_heading_level: 2}, + {toc_min_heading_level: 2, toc_max_heading_level: 6}, + {toc_min_heading_level: 2, toc_max_heading_level: 3}, + {toc_min_heading_level: 3, toc_max_heading_level: 3}, + ], + invalidFrontMatters: [ + [ + {toc_min_heading_level: 4, toc_max_heading_level: 3}, + '"toc_min_heading_level" must be less than or equal to ref:toc_max_heading_level', + ], + [ + {toc_min_heading_level: 6, toc_max_heading_level: 2}, + '"toc_min_heading_level" must be less than or equal to ref:toc_max_heading_level', + ], + ], + }); +}); + +describe('validateShowcaseFrontMatter draft', () => { + testField({ + prefix: 'draft', + validFrontMatters: [{draft: true}, {draft: false}], + convertibleFrontMatter: [ + [{draft: 'true'}, {draft: true}], + [{draft: 'false'}, {draft: false}], + ], + invalidFrontMatters: [ + [{draft: 'yes'}, 'must be a boolean'], + [{draft: 'no'}, 'must be a boolean'], + [{draft: ''}, 'must be a boolean'], + ], + }); +}); + +describe('validateShowcaseFrontMatter unlisted', () => { + testField({ + prefix: 'unlisted', + validFrontMatters: [{unlisted: true}, {unlisted: false}], + convertibleFrontMatter: [ + [{unlisted: 'true'}, {unlisted: true}], + [{unlisted: 'false'}, {unlisted: false}], + ], + invalidFrontMatters: [ + [{unlisted: 'yes'}, 'must be a boolean'], + [{unlisted: 'no'}, 'must be a boolean'], + [{unlisted: ''}, 'must be a boolean'], + ], + }); +}); + +describe('validateShowcaseFrontMatter draft XOR unlisted', () => { + testField({ + prefix: 'draft XOR unlisted', + validFrontMatters: [ + {draft: false}, + {unlisted: false}, + {draft: false, unlisted: false}, + {draft: true, unlisted: false}, + {draft: false, unlisted: true}, + ], + invalidFrontMatters: [ + [ + {draft: true, unlisted: true}, + "Can't be draft and unlisted at the same time.", + ], + ], + }); +}); + +describe('validateShowcaseFrontMatter last_update', () => { + testField({ + prefix: 'last_update', + validFrontMatters: [ + {last_update: undefined}, + {last_update: {author: 'test author', date: undefined}}, + {last_update: {author: undefined, date: '1/1/2000'}}, + {last_update: {author: undefined, date: new Date('1/1/2000')}}, + {last_update: {author: 'test author', date: '1/1/2000'}}, + {last_update: {author: 'test author', date: '1995-12-17T03:24:00'}}, + {last_update: {author: undefined, date: 'December 17, 1995 03:24:00'}}, + ], + invalidFrontMatters: [ + [ + {last_update: null}, + '"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date', + ], + [ + {last_update: {}}, + '"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date', + ], + [ + {last_update: ''}, + '"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date', + ], + [ + {last_update: {invalid: 'key'}}, + '"last_update" does not look like a valid last update object. Please use an author key with a string or a date with a string or Date', + ], + [ + {last_update: {author: 'test author', date: 'I am not a date :('}}, + 'must be a valid date', + ], + [ + {last_update: {author: 'test author', date: '2011-10-45'}}, + 'must be a valid date', + ], + [ + {last_update: {author: 'test author', date: '2011-0-10'}}, + 'must be a valid date', + ], + [ + {last_update: {author: 'test author', date: ''}}, + 'must be a valid date', + ], + ], + }); +}); diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts new file mode 100644 index 0000000000..6669399f90 --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/index.test.ts @@ -0,0 +1,32 @@ +/** + * 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 path from 'path'; +import {loadContext} from '@docusaurus/core/src/server/site'; +import {normalizePluginOptions} from '@docusaurus/utils-validation'; + +import pluginContentPages from '../index'; +import {validateOptions} from '../options'; + +describe('docusaurus-plugin-content-showcase', () => { + it('loads simple showcase', async () => { + const siteDir = path.join(__dirname, '__fixtures__', 'website'); + const context = await loadContext({siteDir}); + const plugin = pluginContentPages( + context, + validateOptions({ + validate: normalizePluginOptions, + options: { + path: 'src/showcase', + }, + }), + ); + const showcaseMetadata = await plugin.loadContent!(); + + expect(showcaseMetadata).toMatchSnapshot(); + }); +}); diff --git a/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts b/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts new file mode 100644 index 0000000000..52d1dd7b71 --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/__tests__/options.test.ts @@ -0,0 +1,63 @@ +/** + * 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 {normalizePluginOptions} from '@docusaurus/utils-validation'; +import {validateOptions, DEFAULT_OPTIONS} from '../options'; +import type {Options} from '@docusaurus/plugin-content-pages'; + +function testValidate(options: Options) { + return validateOptions({validate: normalizePluginOptions, options}); +} +const defaultOptions = { + ...DEFAULT_OPTIONS, + id: 'default', +}; + +describe('normalizeShowcasePluginOptions', () => { + it('returns default options for undefined user options', () => { + expect(testValidate({})).toEqual(defaultOptions); + }); + + it('fills in default options for partially defined user options', () => { + expect(testValidate({path: 'src/foo'})).toEqual({ + ...defaultOptions, + path: 'src/foo', + }); + }); + + it('accepts correctly defined user options', () => { + const userOptions = { + path: 'src/showcase', + routeBasePath: '/showcase', + include: ['**/*.{yaml,yml}'], + exclude: ['**/$*/'], + }; + expect(testValidate(userOptions)).toEqual({ + ...defaultOptions, + ...userOptions, + }); + }); + + it('rejects bad path inputs', () => { + expect(() => { + testValidate({ + // @ts-expect-error: bad attribute + path: 42, + }); + }).toThrowErrorMatchingInlineSnapshot(`""path" must be a string"`); + }); + + it('empty routeBasePath replace default path("/")', () => { + expect( + testValidate({ + routeBasePath: '', + }), + ).toEqual({ + ...defaultOptions, + routeBasePath: '/', + }); + }); +}); diff --git a/packages/docusaurus-plugin-content-showcase/src/index.ts b/packages/docusaurus-plugin-content-showcase/src/index.ts new file mode 100644 index 0000000000..6a80d9d900 --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/index.ts @@ -0,0 +1,123 @@ +/** + * 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 fs from 'fs-extra'; +import path from 'path'; +import { + getFolderContainingFile, + getPluginI18nPath, + Globby, +} from '@docusaurus/utils'; +import Yaml from 'js-yaml'; + +import {validateShowcaseFrontMatter} from './yaml'; +import type {LoadContext, Plugin} from '@docusaurus/types'; +import type {PluginOptions, Content} from '@docusaurus/plugin-content-showcase'; +import type {ShowcaseContentPaths} from './types'; + +export function getContentPathList( + contentPaths: ShowcaseContentPaths, +): string[] { + return [contentPaths.contentPathLocalized, contentPaths.contentPath]; +} + +export default function pluginContentShowcase( + context: LoadContext, + options: PluginOptions, +): Plugin { + const {siteDir, localizationDir} = context; + + const contentPaths: ShowcaseContentPaths = { + contentPath: path.resolve(siteDir, options.path), + contentPathLocalized: getPluginI18nPath({ + localizationDir, + pluginName: 'docusaurus-plugin-content-pages', + pluginId: options.id, + }), + }; + + return { + name: 'docusaurus-plugin-content-showcase', + + // todo doesn't work + // getPathsToWatch() { + // const {include} = options; + // return getContentPathList(contentPaths).flatMap((contentPath) => + // include.map((pattern) => `${contentPath}/${pattern}`), + // ); + // }, + + async loadContent(): Promise { + const {include} = options; + + if (!(await fs.pathExists(contentPaths.contentPath))) { + return null; + } + + // const {baseUrl} = siteConfig; + const showcaseFiles = await Globby(include, { + cwd: contentPaths.contentPath, + ignore: options.exclude, + }); + + async function processShowcaseSourceFile(relativeSource: string) { + // Lookup in localized folder in priority + const contentPath = await getFolderContainingFile( + getContentPathList(contentPaths), + relativeSource, + ); + + const sourcePath = path.join(contentPath, relativeSource); + const rawYaml = await fs.readFile(sourcePath, 'utf-8'); + const unsafeYaml = Yaml.load(rawYaml) as {[key: string]: unknown}; + return validateShowcaseFrontMatter(unsafeYaml); + } + + async function doProcessShowcaseSourceFile(relativeSource: string) { + try { + return await processShowcaseSourceFile(relativeSource); + } catch (err) { + throw new Error( + `Processing of page source file path=${relativeSource} failed.`, + {cause: err as Error}, + ); + } + } + + return { + website: await Promise.all( + showcaseFiles.map(doProcessShowcaseSourceFile), + ), + }; + }, + + async contentLoaded({content, actions}) { + if (!content) { + return; + } + + const {addRoute, createData} = actions; + + const showcaseAllData = await createData( + 'showcaseAll.json', + JSON.stringify(content.website), + ); + + addRoute({ + path: '/showcaseAll', + component: '@theme/Showcase', + modules: { + content: showcaseAllData, + // img: '@site/src/showcase/website/ozaki/aot.jpg', + }, + exact: true, + }); + }, + }; +} + +export {validateOptions} from './options'; diff --git a/packages/docusaurus-plugin-showcase/src/markdownLoader.ts b/packages/docusaurus-plugin-content-showcase/src/markdownLoader.ts similarity index 100% rename from packages/docusaurus-plugin-showcase/src/markdownLoader.ts rename to packages/docusaurus-plugin-content-showcase/src/markdownLoader.ts diff --git a/packages/docusaurus-plugin-showcase/src/options.ts b/packages/docusaurus-plugin-content-showcase/src/options.ts similarity index 61% rename from packages/docusaurus-plugin-showcase/src/options.ts rename to packages/docusaurus-plugin-content-showcase/src/options.ts index 23bbfc4673..3eff51e430 100644 --- a/packages/docusaurus-plugin-showcase/src/options.ts +++ b/packages/docusaurus-plugin-content-showcase/src/options.ts @@ -5,24 +5,16 @@ * LICENSE file in the root directory of this source tree. */ -import { - Joi, - validateFrontMatter, - RouteBasePathSchema, -} from '@docusaurus/utils-validation'; +import {Joi, RouteBasePathSchema} from '@docusaurus/utils-validation'; import {GlobExcludeDefault} from '@docusaurus/utils'; import type {OptionValidationContext} from '@docusaurus/types'; -import type { - PluginOptions, - Options, - ShowcaseFrontMatter, -} from '@docusaurus/plugin-showcase'; +import type {PluginOptions, Options} from '@docusaurus/plugin-content-showcase'; export const DEFAULT_OPTIONS: PluginOptions = { id: 'showcase', path: 'src/showcase/website', // Path to data on filesystem, relative to site dir. routeBasePath: '/', // URL Route. - include: ['**/*.{yml,yaml,md,mdx}'], // Extensions to include. + include: ['**/*.{yml,yaml}'], // Extensions to include. exclude: GlobExcludeDefault, }; @@ -34,15 +26,6 @@ const PluginOptionSchema = Joi.object({ id: Joi.string().default(DEFAULT_OPTIONS.id), }); -const contentAuthorsSchema = Joi.object({ - title: Joi.string().required(), - description: Joi.string().required(), - preview: Joi.string().required(), - website: Joi.string().required(), - source: Joi.string().required(), - tags: Joi.array().items(Joi.string()).required(), -}); - export function validateOptions({ validate, options, @@ -50,9 +33,3 @@ export function validateOptions({ const validatedOptions = validate(PluginOptionSchema, options); return validatedOptions; } - -export function validateShowcaseFrontMatter(frontMatter: { - [key: string]: unknown; -}): ShowcaseFrontMatter { - return validateFrontMatter(frontMatter, contentAuthorsSchema); -} diff --git a/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts b/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts similarity index 94% rename from packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts rename to packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts index d332e799ac..5c0475f5ff 100644 --- a/packages/docusaurus-plugin-showcase/src/plugin-showcase.d.ts +++ b/packages/docusaurus-plugin-content-showcase/src/plugin-content-showcase.d.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -declare module '@docusaurus/plugin-showcase' { +declare module '@docusaurus/plugin-content-showcase' { import type {LoadContext, Plugin} from '@docusaurus/types'; export type Assets = { @@ -43,7 +43,6 @@ declare module '@docusaurus/plugin-showcase' { export type Content = { website: { - type: string; title: string; description: string; preview: string | null; // null = use our serverless screenshot service @@ -59,7 +58,7 @@ declare module '@docusaurus/plugin-showcase' { export default function pluginShowcase( context: LoadContext, options: PluginOptions, - ): Promise>; + ): Promise>; export type ShowcaseMetadata = { /** Path to the Markdown source, with `@site` alias. */ diff --git a/packages/docusaurus-plugin-showcase/src/types.ts b/packages/docusaurus-plugin-content-showcase/src/types.ts similarity index 100% rename from packages/docusaurus-plugin-showcase/src/types.ts rename to packages/docusaurus-plugin-content-showcase/src/types.ts diff --git a/packages/docusaurus-plugin-content-showcase/src/yaml.ts b/packages/docusaurus-plugin-content-showcase/src/yaml.ts new file mode 100644 index 0000000000..dc3550f4cd --- /dev/null +++ b/packages/docusaurus-plugin-content-showcase/src/yaml.ts @@ -0,0 +1,24 @@ +/** + * 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 {Joi, validateFrontMatter} from '@docusaurus/utils-validation'; +import type {ShowcaseFrontMatter} from '@docusaurus/plugin-content-showcase'; + +const showcaseFrontMatterSchema = Joi.object({ + title: Joi.string().required(), + description: Joi.string().required(), + preview: Joi.string().required(), + website: Joi.string().required(), + source: Joi.string().required(), + tags: Joi.array().items(Joi.string()).required(), +}); + +export function validateShowcaseFrontMatter(frontMatter: { + [key: string]: unknown; +}): ShowcaseFrontMatter { + return validateFrontMatter(frontMatter, showcaseFrontMatterSchema); +} diff --git a/packages/docusaurus-plugin-showcase/tsconfig.json b/packages/docusaurus-plugin-content-showcase/tsconfig.json similarity index 100% rename from packages/docusaurus-plugin-showcase/tsconfig.json rename to packages/docusaurus-plugin-content-showcase/tsconfig.json diff --git a/packages/docusaurus-plugin-showcase/README.md b/packages/docusaurus-plugin-showcase/README.md deleted file mode 100644 index 1d075c625a..0000000000 --- a/packages/docusaurus-plugin-showcase/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# `@docusaurus/plugin-showcase` - -Showcase plugin for Docusaurus. - -## Usage - -See [plugin-showcase documentation](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-showcase). diff --git a/packages/docusaurus-plugin-showcase/src/index.ts b/packages/docusaurus-plugin-showcase/src/index.ts deleted file mode 100644 index 9be678be10..0000000000 --- a/packages/docusaurus-plugin-showcase/src/index.ts +++ /dev/null @@ -1,257 +0,0 @@ -/** - * 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 fs from 'fs-extra'; -import path from 'path'; -import { - DEFAULT_PLUGIN_ID, - addTrailingPathSeparator, - aliasedSitePath, - docuHash, - getFolderContainingFile, - getPluginI18nPath, - Globby, - parseMarkdownFile, - aliasedSitePathToRelativePath, - createAbsoluteFilePathMatcher, -} from '@docusaurus/utils'; -import Yaml from 'js-yaml'; - -import {validateShowcaseFrontMatter} from './options'; -import type {LoadContext, Plugin, RouteMetadata} from '@docusaurus/types'; -import type {PluginOptions, Content} from '@docusaurus/plugin-showcase'; -import type {ShowcaseContentPaths} from './types'; - -export function getContentPathList( - contentPaths: ShowcaseContentPaths, -): string[] { - return [contentPaths.contentPathLocalized, contentPaths.contentPath]; -} - -const isMarkdownSource = (source: string) => - source.endsWith('.md') || source.endsWith('.mdx'); - -export default function pluginContentShowcase( - context: LoadContext, - options: PluginOptions, -): Plugin { - const {siteConfig, siteDir, generatedFilesDir, localizationDir} = context; - - const contentPaths: ShowcaseContentPaths = { - contentPath: path.resolve(siteDir, options.path), - contentPathLocalized: getPluginI18nPath({ - localizationDir, - pluginName: 'docusaurus-plugin-content-pages', - pluginId: options.id, - }), - }; - - const pluginDataDirRoot = path.join( - generatedFilesDir, - 'docusaurus-plugin-showcase', - ); - const dataDir = path.join(pluginDataDirRoot, options.id ?? DEFAULT_PLUGIN_ID); - - return { - name: 'docusaurus-plugin-showcase', - - // todo doesn't work - // getPathsToWatch() { - // const {include} = options; - // return getContentPathList(contentPaths).flatMap((contentPath) => - // include.map((pattern) => `${contentPath}/${pattern}`), - // ); - // }, - - async loadContent() { - const {include} = options; - - if (!(await fs.pathExists(contentPaths.contentPath))) { - return null; - } - - // const {baseUrl} = siteConfig; - const showcaseFiles = await Globby(include, { - cwd: contentPaths.contentPath, - ignore: options.exclude, - }); - - async function processShowcaseSourceFile(relativeSource: string) { - // Lookup in localized folder in priority - const contentPath = await getFolderContainingFile( - getContentPathList(contentPaths), - relativeSource, - ); - - const sourcePath = path.join(contentPath, relativeSource); - const aliasedSourcePath = aliasedSitePath(sourcePath, siteDir); - if (!isMarkdownSource(sourcePath)) { - const rawYaml = await fs.readFile(sourcePath, 'utf-8'); - const unsafeYaml = Yaml.load(rawYaml) as {[key: string]: unknown}; - const yaml = validateShowcaseFrontMatter(unsafeYaml); - return { - type: 'yaml', - ...yaml, - }; - } - const rawMarkdown = await fs.readFile(sourcePath, 'utf-8'); - const {frontMatter: unsafeFrontMatter, content} = - await parseMarkdownFile({ - filePath: sourcePath, - fileContent: rawMarkdown, - parseFrontMatter: siteConfig.markdown?.parseFrontMatter, - }); - const frontMatter = validateShowcaseFrontMatter(unsafeFrontMatter); - return { - type: 'markdown', - ...frontMatter, - content, - sourcePath: aliasedSourcePath, - }; - } - - async function doProcessShowcaseSourceFile(relativeSource: string) { - try { - return await processShowcaseSourceFile(relativeSource); - } catch (err) { - throw new Error( - `Processing of page source file path=${relativeSource} failed.`, - {cause: err as Error}, - ); - } - } - - return { - website: await Promise.all( - showcaseFiles.map(doProcessShowcaseSourceFile), - ), - }; - }, - - async contentLoaded({content, actions}) { - if (!content) { - return; - } - - const {addRoute, createData} = actions; - - function createPageRouteMetadata( - metadata: Content['website'][number], - ): RouteMetadata { - return { - sourceFilePath: aliasedSitePathToRelativePath(metadata.sourcePath!), - // TODO add support for last updated date in the page plugin - // at least for Markdown files - // lastUpdatedAt: metadata.lastUpdatedAt, - lastUpdatedAt: undefined, - }; - } - - await Promise.all( - content.website.map(async (item) => { - if (item.type === 'yaml') { - return; - } - await createData( - `${docuHash(item.sourcePath!)}.json`, - JSON.stringify(item), - ); - - const routeMetadata = createPageRouteMetadata(item); - - const mdxPath = aliasedSitePathToRelativePath(item.sourcePath!); - console.log('mdxPath', mdxPath); - - addRoute({ - path: `/showcaseAll/${item.title}`, - component: '@theme/ShowcaseDetails', - metadata: routeMetadata, - modules: { - content: item.sourcePath!, - }, - exact: true, - }); - }), - ); - - const showcaseAllData = await createData( - 'showcaseAll.json', - JSON.stringify(content.website), - ); - - addRoute({ - path: '/showcaseAll', - component: '@theme/Showcase', - modules: { - content: showcaseAllData, - }, - exact: true, - }); - }, - - configureWebpack() { - const contentDirs = getContentPathList(contentPaths); - - return { - resolve: { - alias: { - '~showcase': pluginDataDirRoot, - }, - }, - module: { - rules: [ - { - test: /\.mdx?$/i, - include: contentDirs - // Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970 - .map(addTrailingPathSeparator), - use: [ - { - loader: require.resolve('@docusaurus/mdx-loader'), - options: { - staticDirs: siteConfig.staticDirectories.map((dir) => - path.resolve(siteDir, dir), - ), - siteDir, - isMDXPartial: createAbsoluteFilePathMatcher( - options.exclude, - contentDirs, - ), - metadataPath: (mdxPath: string) => { - // Note that metadataPath must be the same/in-sync as - // the path from createData for each MDX. - const aliasedPath = aliasedSitePath(mdxPath, siteDir); - return path.join( - dataDir, - `${docuHash(aliasedPath)}.json`, - ); - }, - // Assets allow to convert some relative images paths to - // require() calls - createAssets: ({ - frontMatter, - }: { - frontMatter: Content['website'][number]; - }) => ({ - image: frontMatter.preview, - }), - markdownConfig: siteConfig.markdown, - }, - }, - { - loader: path.resolve(__dirname, './markdownLoader.js'), - }, - ].filter(Boolean), - }, - ], - }, - }; - }, - }; -} - -export {validateOptions} from './options'; diff --git a/packages/docusaurus-theme-classic/package.json b/packages/docusaurus-theme-classic/package.json index 05d8caf873..1b95a08a53 100644 --- a/packages/docusaurus-theme-classic/package.json +++ b/packages/docusaurus-theme-classic/package.json @@ -26,7 +26,7 @@ "@docusaurus/plugin-content-blog": "3.0.0", "@docusaurus/plugin-content-docs": "3.0.0", "@docusaurus/plugin-content-pages": "3.0.0", - "@docusaurus/plugin-showcase": "3.0.0", + "@docusaurus/plugin-content-showcase": "3.0.0", "@docusaurus/theme-common": "3.0.0", "@docusaurus/theme-translations": "3.0.0", "@docusaurus/types": "3.0.0", diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 1d891565e5..d35e90336d 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -248,7 +248,7 @@ declare module '@theme/BlogPostItems' { } declare module '@theme/ShowcaseDetails' { - import type {Content} from '@docusaurus/plugin-showcase'; + import type {Content} from '@docusaurus/plugin-content-showcase'; export type User = Content['website'][number]; @@ -260,7 +260,7 @@ declare module '@theme/ShowcaseDetails' { } declare module '@theme/Showcase' { - import type {Content} from '@docusaurus/plugin-showcase'; + import type {Content} from '@docusaurus/plugin-content-showcase'; export type User = Content['website'][number]; diff --git a/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx b/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx index 0acd3df1a1..a79c0c6457 100644 --- a/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Showcase/index.tsx @@ -21,7 +21,7 @@ import ShowcaseTooltip from '@theme/Showcase/ShowcaseTooltip'; import ShowcaseTagSelect from '@theme/Showcase/ShowcaseTagSelect'; import ShowcaseFilterToggle from '@theme/Showcase/ShowcaseFilterToggle'; import type {Operator} from '@theme/Showcase/ShowcaseFilterToggle'; -import type {TagType} from '@docusaurus/plugin-showcase'; +import type {TagType} from '@docusaurus/plugin-content-showcase'; import styles from './styles.module.css'; type Users = User[];