diff --git a/src/compiler/docs/generate-doc-data.ts b/src/compiler/docs/generate-doc-data.ts index 0237900c35a..25174b132e7 100644 --- a/src/compiler/docs/generate-doc-data.ts +++ b/src/compiler/docs/generate-doc-data.ts @@ -60,6 +60,7 @@ const getDocsComponents = async ( usagesDir, tag: cmp.tagName, readme, + overview: cmp.docs.text, usage, docs: generateDocs(readme, cmp.docs), docsTags: cmp.docs.tags, diff --git a/src/compiler/docs/readme/markdown-overview.ts b/src/compiler/docs/readme/markdown-overview.ts index b2d4dc451e5..f9ace8783ed 100644 --- a/src/compiler/docs/readme/markdown-overview.ts +++ b/src/compiler/docs/readme/markdown-overview.ts @@ -3,7 +3,7 @@ * @param overview a component-level comment string to place in a markdown file * @returns The generated Overview section. If the provided overview is empty, return an empty list */ -export const overviewToMarkdown = (overview: string): ReadonlyArray => { +export const overviewToMarkdown = (overview: string | undefined): ReadonlyArray => { if (!overview) { return []; } diff --git a/src/compiler/docs/readme/output-docs.ts b/src/compiler/docs/readme/output-docs.ts index 34bdb0bf81f..25e8a533474 100644 --- a/src/compiler/docs/readme/output-docs.ts +++ b/src/compiler/docs/readme/output-docs.ts @@ -55,7 +55,7 @@ export const generateMarkdown = ( '', '', ...getDocsDeprecation(cmp), - ...overviewToMarkdown(cmp.docs), + ...overviewToMarkdown(cmp.overview), ...usageToMarkdown(cmp.usage), ...propsToMarkdown(cmp.props), ...eventsToMarkdown(cmp.events), diff --git a/src/compiler/docs/test/generate-doc-data.spec.ts b/src/compiler/docs/test/generate-doc-data.spec.ts new file mode 100644 index 00000000000..b0fed8fec7b --- /dev/null +++ b/src/compiler/docs/test/generate-doc-data.spec.ts @@ -0,0 +1,200 @@ +import { mockBuildCtx, mockCompilerCtx, mockModule, mockValidatedConfig } from '@stencil/core/testing'; + +import type * as d from '../../../declarations'; +import { getComponentsFromModules } from '../../output-targets/output-utils'; +import { stubComponentCompilerMeta } from '../../types/tests/ComponentCompilerMeta.stub'; +import { AUTO_GENERATE_COMMENT } from '../constants'; +import { generateDocData } from '../generate-doc-data'; + +describe('generate-doc-data', () => { + describe('getDocsComponents', () => { + let moduleCmpWithJsdoc: d.Module; + let moduleCmpNoJsdoc: d.Module; + + beforeEach(() => { + moduleCmpWithJsdoc = mockModule({ + cmps: [ + stubComponentCompilerMeta({ + docs: { + tags: [], + text: 'This is the overview of `my-component`', + }, + }), + ], + }); + moduleCmpNoJsdoc = mockModule({ + cmps: [ + stubComponentCompilerMeta({ + docs: { + tags: [], + text: '', + }, + }), + ], + }); + }); + + /** + * Setup function for the {@link generateDocData} function exported by the module under test + * @param moduleMap a map of {@link d.ModuleMap} entities to add to the returned compiler and build contexts + * @returns the arguments required to invoke the method under test + */ + const setup = ( + moduleMap: d.ModuleMap + ): { validatedConfig: d.ValidatedConfig; compilerCtx: d.CompilerCtx; buildCtx: d.BuildCtx } => { + const validatedConfig: d.ValidatedConfig = mockValidatedConfig(); + + const compilerCtx: d.CompilerCtx = mockCompilerCtx(validatedConfig); + compilerCtx.moduleMap = moduleMap; + + const modules = Array.from(compilerCtx.moduleMap.values()); + const buildCtx: d.BuildCtx = mockBuildCtx(validatedConfig, compilerCtx); + buildCtx.moduleFiles = modules; + buildCtx.components = getComponentsFromModules(modules); + + return { validatedConfig, compilerCtx, buildCtx }; + }; + + describe('component JSDoc overview', () => { + it("takes the value from the component's JSDoc", async () => { + const moduleMap: d.ModuleMap = new Map(); + moduleMap.set('path/to/component.tsx', moduleCmpWithJsdoc); + const { validatedConfig, compilerCtx, buildCtx } = setup(moduleMap); + + const generatedDocData = await generateDocData(validatedConfig, compilerCtx, buildCtx); + + expect(generatedDocData.components).toHaveLength(1); + const componentDocData = generatedDocData.components[0]; + expect(componentDocData.overview).toBe('This is the overview of `my-component`'); + }); + + it('sets the value to the empty string when there is no JSDoc', async () => { + const moduleMap: d.ModuleMap = new Map(); + moduleMap.set('path/to/component.tsx', moduleCmpNoJsdoc); + const { validatedConfig, compilerCtx, buildCtx } = setup(moduleMap); + + const generatedDocData = await generateDocData(validatedConfig, compilerCtx, buildCtx); + + expect(generatedDocData.components).toHaveLength(1); + const componentDocData = generatedDocData.components[0]; + expect(componentDocData.overview).toBe(''); + }); + }); + + describe('docs content', () => { + it("sets the field's contents to the jsdoc text if present", async () => { + const moduleMap: d.ModuleMap = new Map(); + moduleMap.set('path/to/component.tsx', moduleCmpWithJsdoc); + const { validatedConfig, compilerCtx, buildCtx } = setup(moduleMap); + + const generatedDocData = await generateDocData(validatedConfig, compilerCtx, buildCtx); + + expect(generatedDocData.components).toHaveLength(1); + const componentDocData = generatedDocData.components[0]; + expect(componentDocData.docs).toBe('This is the overview of `my-component`'); + }); + + it("sets the field's contents to an empty string if neither the readme, nor jsdoc are set", async () => { + const moduleMap: d.ModuleMap = new Map(); + moduleMap.set('path/to/component.tsx', moduleCmpNoJsdoc); + const { validatedConfig, compilerCtx, buildCtx } = setup(moduleMap); + + const generatedDocData = await generateDocData(validatedConfig, compilerCtx, buildCtx); + + expect(generatedDocData.components).toHaveLength(1); + const componentDocData = generatedDocData.components[0]; + expect(componentDocData.docs).toBe(''); + }); + + it("sets the field's contents to an empty string if the readme doesn't contain the autogenerated comment", async () => { + const moduleMap: d.ModuleMap = new Map(); + moduleMap.set('path/to/component.tsx', moduleCmpNoJsdoc); + const { validatedConfig, compilerCtx, buildCtx } = setup(moduleMap); + + await compilerCtx.fs.writeFile('readme.md', 'this is manually generated user content'); + + const generatedDocData = await generateDocData(validatedConfig, compilerCtx, buildCtx); + + expect(generatedDocData.components).toHaveLength(1); + const componentDocData = generatedDocData.components[0]; + expect(componentDocData.docs).toBe(''); + }); + + it("sets the field's contents to manually generated content when the autogenerated comment is present", async () => { + const moduleMap: d.ModuleMap = new Map(); + moduleMap.set('path/to/component.tsx', moduleCmpNoJsdoc); + const { validatedConfig, compilerCtx, buildCtx } = setup(moduleMap); + + await compilerCtx.fs.writeFile( + 'readme.md', + `this is manually generated user content\n${AUTO_GENERATE_COMMENT}\nauto-generated content` + ); + + const generatedDocData = await generateDocData(validatedConfig, compilerCtx, buildCtx); + + expect(generatedDocData.components).toHaveLength(1); + const componentDocData = generatedDocData.components[0]; + expect(componentDocData.docs).toBe('this is manually generated user content'); + }); + + it("sets the field's contents to a subset of the manually generated content", async () => { + const moduleMap: d.ModuleMap = new Map(); + moduleMap.set('path/to/component.tsx', moduleCmpNoJsdoc); + const { validatedConfig, compilerCtx, buildCtx } = setup(moduleMap); + + const readmeContent = ` +this is manually generated user content + +# user header +user content + +# another user header +more user content + +${AUTO_GENERATE_COMMENT} + +#some-header + +auto-generated content +`; + await compilerCtx.fs.writeFile('readme.md', readmeContent); + + const generatedDocData = await generateDocData(validatedConfig, compilerCtx, buildCtx); + + expect(generatedDocData.components).toHaveLength(1); + const componentDocData = generatedDocData.components[0]; + expect(componentDocData.docs).toBe('this is manually generated user content'); + }); + + it("sets the field's contents to a an empty string when the manually generated content starts with a '#'", async () => { + const moduleMap: d.ModuleMap = new Map(); + moduleMap.set('path/to/component.tsx', moduleCmpNoJsdoc); + const { validatedConfig, compilerCtx, buildCtx } = setup(moduleMap); + + const readmeContent = ` +# header that leads to skipping +this is manually generated user content + +# user header +user content + +# another user header +more user content + +${AUTO_GENERATE_COMMENT} + +#some-header + +auto-generated content +`; + await compilerCtx.fs.writeFile('readme.md', readmeContent); + + const generatedDocData = await generateDocData(validatedConfig, compilerCtx, buildCtx); + + expect(generatedDocData.components).toHaveLength(1); + const componentDocData = generatedDocData.components[0]; + expect(componentDocData.docs).toBe(''); + }); + }); + }); +}); diff --git a/src/declarations/stencil-public-docs.ts b/src/declarations/stencil-public-docs.ts index 1fd254cdc68..18b6a795fa1 100644 --- a/src/declarations/stencil-public-docs.ts +++ b/src/declarations/stencil-public-docs.ts @@ -19,6 +19,10 @@ export interface JsonDocsComponent { readme: string; docs: string; docsTags: JsonDocsTag[]; + /** + * The comment found at in a class-level JSDoc for a Stencil component. + */ + overview?: string; usage: JsonDocsUsage; props: JsonDocsProp[]; methods: JsonDocsMethod[];