diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/authors.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/authors.test.ts index c1bf962c90ac..f4750f88931f 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/authors.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/authors.test.ts @@ -7,7 +7,11 @@ import {fromPartial, type PartialDeep} from '@total-typescript/shoehorn'; import {getBlogPostAuthors, groupBlogPostsByAuthorKey} from '../authors'; -import type {AuthorsMap, BlogPost} from '@docusaurus/plugin-content-blog'; +import type { + AuthorAttributes, + AuthorsMap, + BlogPost, +} from '@docusaurus/plugin-content-blog'; function post(partial: PartialDeep): BlogPost { return fromPartial(partial); @@ -268,11 +272,176 @@ describe('getBlogPostAuthors', () => { ]); }); - it('can read authors Author', () => { + it('read different values from socials', () => { + function testSocials(socials: AuthorAttributes['socials'] | undefined) { + return getBlogPostAuthors({ + frontMatter: { + authors: { + name: 'Sébastien Lorber', + title: 'maintainer', + socials, + }, + }, + authorsMap: undefined, + baseUrl: '/', + }); + } + + // @ts-expect-error test + expect(() => testSocials(null)).not.toThrow(); + // @ts-expect-error test + expect(testSocials(null)).toEqual([ + { + name: 'Sébastien Lorber', + title: 'maintainer', + imageURL: undefined, + socials: {}, + key: null, + page: null, + }, + ]); + expect(() => () => testSocials(undefined)).not.toThrow(); + // @ts-expect-error test + expect(() => testSocials({twitter: undefined})) + .toThrowErrorMatchingInlineSnapshot(` + "Author socials should be usernames/userIds/handles, or fully qualified HTTP(s) absolute URLs. + Social platform 'twitter' has illegal value 'undefined'" + `); + expect( + // @ts-expect-error test + () => testSocials({twitter: null}), + ).toThrowErrorMatchingInlineSnapshot(` + "Author socials should be usernames/userIds/handles, or fully qualified HTTP(s) absolute URLs. + Social platform 'twitter' has illegal value 'null'" + `); + }); + + it('can read empty socials', () => { + expect( + getBlogPostAuthors({ + frontMatter: { + authors: { + name: 'Sébastien Lorber', + title: 'maintainer', + socials: {}, + }, + }, + authorsMap: undefined, + baseUrl: '/', + }), + ).toEqual([ + { + name: 'Sébastien Lorber', + title: 'maintainer', + imageURL: undefined, + socials: {}, + key: null, + page: null, + }, + ]); + }); + + it('can normalize full socials from Author', () => { + expect( + getBlogPostAuthors({ + frontMatter: { + authors: { + name: 'Sébastien Lorber', + title: 'maintainer', + socials: { + github: 'https://github.com/slorber', + linkedin: 'https://www.linkedin.com/in/sebastienlorber/', + stackoverflow: 'https://stackoverflow.com/users/82609', + twitter: 'https://twitter.com/sebastienlorber', + x: 'https://x.com/sebastienlorber', + }, + }, + }, + authorsMap: undefined, + baseUrl: '/', + }), + ).toEqual([ + { + name: 'Sébastien Lorber', + title: 'maintainer', + imageURL: undefined, + key: null, + socials: { + github: 'https://github.com/slorber', + linkedin: 'https://www.linkedin.com/in/sebastienlorber/', + stackoverflow: 'https://stackoverflow.com/users/82609', + twitter: 'https://twitter.com/sebastienlorber', + x: 'https://x.com/sebastienlorber', + }, + page: null, + }, + ]); + }); + + it('can normalize handle socials from Author', () => { + expect( + getBlogPostAuthors({ + frontMatter: { + authors: { + name: 'Sébastien Lorber', + title: 'maintainer', + socials: { + github: 'slorber', + x: 'sebastienlorber', + linkedin: 'sebastienlorber', + stackoverflow: '82609', + twitter: 'sebastienlorber', + }, + }, + }, + authorsMap: undefined, + baseUrl: '/', + }), + ).toEqual([ + { + name: 'Sébastien Lorber', + title: 'maintainer', + imageURL: undefined, + key: null, + socials: { + github: 'https://github.com/slorber', + linkedin: 'https://www.linkedin.com/in/sebastienlorber/', + stackoverflow: 'https://stackoverflow.com/users/82609', + twitter: 'https://twitter.com/sebastienlorber', + x: 'https://x.com/sebastienlorber', + }, + page: null, + }, + ]); + }); + + it('can normalize socials from Author[]', () => { expect( getBlogPostAuthors({ frontMatter: { - authors: {name: 'Sébastien Lorber', title: 'maintainer'}, + authors: [ + { + name: 'Sébastien Lorber', + title: 'maintainer', + socials: { + github: 'slorber', + x: 'sebastienlorber', + linkedin: 'sebastienlorber', + stackoverflow: '82609', + twitter: 'sebastienlorber', + }, + }, + { + name: 'Seb', + socials: { + github: 'https://github.com/slorber', + linkedin: 'https://www.linkedin.com/in/sebastienlorber/', + stackoverflow: 'https://stackoverflow.com/users/82609', + twitter: 'https://twitter.com/sebastienlorber', + x: 'https://x.com/sebastienlorber', + }, + }, + ], }, authorsMap: undefined, baseUrl: '/', @@ -283,6 +452,26 @@ describe('getBlogPostAuthors', () => { title: 'maintainer', imageURL: undefined, key: null, + socials: { + github: 'https://github.com/slorber', + linkedin: 'https://www.linkedin.com/in/sebastienlorber/', + stackoverflow: 'https://stackoverflow.com/users/82609', + twitter: 'https://twitter.com/sebastienlorber', + x: 'https://x.com/sebastienlorber', + }, + page: null, + }, + { + name: 'Seb', + imageURL: undefined, + key: null, + socials: { + github: 'https://github.com/slorber', + linkedin: 'https://www.linkedin.com/in/sebastienlorber/', + stackoverflow: 'https://stackoverflow.com/users/82609', + twitter: 'https://twitter.com/sebastienlorber', + x: 'https://x.com/sebastienlorber', + }, page: null, }, ]); @@ -293,8 +482,13 @@ describe('getBlogPostAuthors', () => { getBlogPostAuthors({ frontMatter: { authors: [ - {name: 'Sébastien Lorber', title: 'maintainer'}, - {name: 'Yangshun Tay'}, + { + name: 'Sébastien Lorber', + title: 'maintainer', + }, + { + name: 'Yangshun Tay', + }, ], }, authorsMap: undefined, @@ -306,9 +500,16 @@ describe('getBlogPostAuthors', () => { title: 'maintainer', imageURL: undefined, key: null, + socials: {}, + page: null, + }, + { + name: 'Yangshun Tay', + imageURL: undefined, + socials: {}, + key: null, page: null, }, - {name: 'Yangshun Tay', imageURL: undefined, key: null, page: null}, ]); }); @@ -323,7 +524,12 @@ describe('getBlogPostAuthors', () => { title: 'Yangshun title local override', extra: 42, }, - {name: 'Alexey'}, + { + name: 'Alexey', + socials: { + github: 'lex111', + }, + }, ], }, authorsMap: { @@ -355,10 +561,19 @@ describe('getBlogPostAuthors', () => { name: 'Yangshun Tay', title: 'Yangshun title local override', extra: 42, + socials: {}, imageURL: undefined, page: null, }, - {name: 'Alexey', imageURL: undefined, key: null, page: null}, + { + name: 'Alexey', + imageURL: undefined, + key: null, + page: null, + socials: { + github: 'https://github.com/lex111', + }, + }, ]); }); @@ -627,6 +842,7 @@ describe('getBlogPostAuthors', () => { imageURL: './ozaki.png', key: null, page: null, + socials: {}, }, ]); expect(() => withoutBaseUrlTest).not.toThrow(); @@ -635,6 +851,7 @@ describe('getBlogPostAuthors', () => { imageURL: './ozaki.png', key: null, page: null, + socials: {}, }, ]); }); @@ -654,6 +871,7 @@ describe('getBlogPostAuthors', () => { imageURL: '/ozaki.png', key: null, page: null, + socials: {}, }, ]); }); @@ -673,6 +891,7 @@ describe('getBlogPostAuthors', () => { imageURL: '/img/ozaki.png', key: null, page: null, + socials: {}, }, ]); }); @@ -692,6 +911,7 @@ describe('getBlogPostAuthors', () => { imageURL: '/img/ozaki.png', key: null, page: null, + socials: {}, }, ]); }); @@ -711,6 +931,7 @@ describe('getBlogPostAuthors', () => { imageURL: '/img/img/ozaki.png', key: null, page: null, + socials: {}, }, ]); }); diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/authorsMap.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/authorsMap.test.ts index f9442ee82688..648baed02203 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/authorsMap.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/authorsMap.test.ts @@ -308,7 +308,7 @@ describe('authors socials', () => { }); it('throw socials that are not strings', () => { - const authorsMap: AuthorsMapInput = { + const socialNumber: AuthorsMapInput = { ozaki: { name: 'ozaki', socials: { @@ -318,11 +318,39 @@ describe('authors socials', () => { }, }; + const socialNull: AuthorsMapInput = { + ozaki: { + name: 'ozaki', + socials: { + // @ts-expect-error: for tests + twitter: null, + }, + }, + }; + + const socialNull2: AuthorsMapInput = { + ozaki: { + name: 'ozaki', + // @ts-expect-error: for tests + socials: null, + }, + }; + expect(() => - validateAuthorsMap(authorsMap), + validateAuthorsMap(socialNumber), ).toThrowErrorMatchingInlineSnapshot( `""ozaki.socials.twitter" must be a string"`, ); + expect(() => + validateAuthorsMap(socialNull), + ).toThrowErrorMatchingInlineSnapshot( + `""ozaki.socials.twitter" must be a string"`, + ); + expect(() => + validateAuthorsMap(socialNull2), + ).toThrowErrorMatchingInlineSnapshot( + `""ozaki.socials" should be an author object containing properties like name, title, and imageURL."`, + ); }); it('throw socials that are objects', () => { diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts index 7c9247822d73..35c695d8655c 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts @@ -224,6 +224,7 @@ describe('blog plugin', () => { imageURL: undefined, key: null, page: null, + socials: {}, }, { email: 'lorber.sebastien@gmail.com', @@ -231,6 +232,7 @@ describe('blog plugin', () => { name: 'Sébastien Lorber (translated)', title: 'Docusaurus maintainer (translated)', imageURL: undefined, + socials: undefined, page: {permalink: '/blog/authors/slorber-custom-permalink-localized'}, }, ], diff --git a/packages/docusaurus-plugin-content-blog/src/authors.ts b/packages/docusaurus-plugin-content-blog/src/authors.ts index bc6b152157b2..170ef9e73b3a 100644 --- a/packages/docusaurus-plugin-content-blog/src/authors.ts +++ b/packages/docusaurus-plugin-content-blog/src/authors.ts @@ -7,6 +7,7 @@ import _ from 'lodash'; import {normalizeUrl} from '@docusaurus/utils'; +import {normalizeSocials} from './authorsSocials'; import type { Author, AuthorsMap, @@ -107,7 +108,10 @@ function getFrontMatterAuthors(params: AuthorsParam): Author[] { // becoming a name and may end up unnoticed return {key: authorInput}; } - return authorInput; + return { + ...authorInput, + socials: normalizeSocials(authorInput.socials ?? {}), + }; } return Array.isArray(frontMatter.authors) diff --git a/packages/docusaurus-plugin-content-blog/src/authorsSocials.ts b/packages/docusaurus-plugin-content-blog/src/authorsSocials.ts index 8ca12169a3b0..b190e3e09dc2 100644 --- a/packages/docusaurus-plugin-content-blog/src/authorsSocials.ts +++ b/packages/docusaurus-plugin-content-blog/src/authorsSocials.ts @@ -41,6 +41,12 @@ type SocialEntry = [string, string]; function normalizeSocialEntry([platform, value]: SocialEntry): SocialEntry { const normalizer = PredefinedPlatformNormalizers[platform.toLowerCase()]; + if (typeof value !== 'string') { + throw new Error( + `Author socials should be usernames/userIds/handles, or fully qualified HTTP(s) absolute URLs. +Social platform '${platform}' has illegal value '${value}'`, + ); + } const isAbsoluteUrl = value.startsWith('http://') || value.startsWith('https://'); if (isAbsoluteUrl) {