From 063ba6e264ec44f797531c7612b814bd331c5f59 Mon Sep 17 00:00:00 2001 From: Shinigami Date: Thu, 30 Mar 2023 17:02:32 +0200 Subject: [PATCH] test: separate jsdoc tag tests (#1969) --- scripts/apidoc/signature.ts | 4 +- .../apidoc/examplesAndDeprecations.spec.ts | 124 --------------- test/scripts/apidoc/verify-jsdoc-tags.spec.ts | 150 ++++++++++++++++++ 3 files changed, 153 insertions(+), 125 deletions(-) delete mode 100644 test/scripts/apidoc/examplesAndDeprecations.spec.ts create mode 100644 test/scripts/apidoc/verify-jsdoc-tags.spec.ts diff --git a/scripts/apidoc/signature.ts b/scripts/apidoc/signature.ts index b3bd4523681..f9364a04eb1 100644 --- a/scripts/apidoc/signature.ts +++ b/scripts/apidoc/signature.ts @@ -40,8 +40,10 @@ function prettifyMethodName(method: string): string { ); } +export const MISSING_DESCRIPTION = 'Missing'; + export function toBlock(comment?: Comment): string { - return joinTagParts(comment?.summary) || 'Missing'; + return joinTagParts(comment?.summary) || MISSING_DESCRIPTION; } export function stripAbsoluteFakerUrls(markdown: string): string { diff --git a/test/scripts/apidoc/examplesAndDeprecations.spec.ts b/test/scripts/apidoc/examplesAndDeprecations.spec.ts deleted file mode 100644 index 5ff635b202c..00000000000 --- a/test/scripts/apidoc/examplesAndDeprecations.spec.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { mkdirSync, writeFileSync } from 'node:fs'; -import { resolve } from 'node:path'; -import type { SpyInstance } from 'vitest'; -import { - afterAll, - beforeAll, - beforeEach, - describe, - expect, - it, - vi, -} from 'vitest'; -import { - analyzeSignature, - initMarkdownRenderer, -} from '../../../scripts/apidoc/signature'; -import { - extractDeprecated, - extractRawExamples, - extractSeeAlsos, - extractSince, - extractTagContent, -} from '../../../scripts/apidoc/typedoc'; -import { loadProjectModules } from './utils'; - -/* - * This test ensures, that every method - * - has working examples - * - and running these do not log anything, unless the method is deprecated - */ - -beforeAll(initMarkdownRenderer); - -describe('examples and deprecations', () => { - const modules = loadProjectModules(); - - const consoleSpies: SpyInstance[] = Object.keys(console) - .filter((key) => typeof console[key] === 'function') - .map((methodName) => vi.spyOn(console, methodName as keyof typeof console)); - - afterAll(() => { - for (const spy of consoleSpies) { - spy.mockRestore(); - } - }); - - describe.each(Object.entries(modules))('%s', (moduleName, methodsByName) => { - beforeEach(() => { - for (const spy of consoleSpies) { - spy.mockReset(); - } - }); - - // eslint-disable-next-line @typescript-eslint/no-misused-promises - it.each(Object.entries(methodsByName))( - '%s', - async (methodName, signature) => { - // Extract examples and make them runnable - const examples = extractRawExamples(signature).join('').trim(); - - expect( - examples, - `${moduleName}.${methodName} to have examples` - ).not.toBe(''); - - // Save examples to a file to run it - const dir = resolve(__dirname, 'temp', moduleName); - mkdirSync(dir, { recursive: true }); - const path = resolve(dir, `${methodName}.ts`); - const imports = [...new Set(examples.match(/faker[^\.]*(?=\.)/g))]; - writeFileSync( - path, - `import { ${imports.join( - ', ' - )} } from '../../../../../src';\n\n${examples}` - ); - - // Run the examples - await import(path); - - // Verify logging - const deprecatedFlag = extractDeprecated(signature) !== undefined; - if (deprecatedFlag) { - expect(consoleSpies[1]).toHaveBeenCalled(); - expect( - extractTagContent('@deprecated', signature).join(''), - '@deprecated tag without message' - ).not.toBe(''); - } else { - for (const spy of consoleSpies) { - expect(spy).not.toHaveBeenCalled(); - } - } - - // Verify @param tags - analyzeSignature(signature, moduleName, methodName).parameters.forEach( - (param) => { - const { name, description } = param; - const plainDescription = description.replace(/<[^>]+>/g, '').trim(); - expect( - plainDescription, - `Expect param ${name} to have a description` - ).not.toBe('Missing'); - } - ); - - // Verify @see tag - extractSeeAlsos(signature).forEach((link) => { - if (link.startsWith('faker.')) { - // Expected @see faker.xxx.yyy() - expect(link, 'Expect method reference to contain ()').toContain( - '(' - ); - expect(link, 'Expect method reference to contain ()').toContain( - ')' - ); - } - }); - - expect(extractSince(signature), '@since to be present').toBeTruthy(); - } - ); - }); -}); diff --git a/test/scripts/apidoc/verify-jsdoc-tags.spec.ts b/test/scripts/apidoc/verify-jsdoc-tags.spec.ts new file mode 100644 index 00000000000..602864b13be --- /dev/null +++ b/test/scripts/apidoc/verify-jsdoc-tags.spec.ts @@ -0,0 +1,150 @@ +import { mkdirSync, rmdirSync, writeFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import validator from 'validator'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; +import { + analyzeSignature, + initMarkdownRenderer, + MISSING_DESCRIPTION, +} from '../../../scripts/apidoc/signature'; +import { + extractDeprecated, + extractRawExamples, + extractSeeAlsos, + extractSince, + extractTagContent, +} from '../../../scripts/apidoc/typedoc'; +import { loadProjectModules } from './utils'; + +// This test ensures, that every method +// - has working examples +// - running these do not log anything, unless the method is deprecated + +beforeAll(initMarkdownRenderer); + +const tempDir = resolve(__dirname, 'temp'); + +afterAll(() => { + // Remove temp folder + rmdirSync(tempDir, { recursive: true }); +}); + +describe('verify JSDoc tags', () => { + const modules = loadProjectModules(); + + function resolveDirToModule(moduleName: string): string { + return resolve(tempDir, moduleName); + } + + function resolvePathToMethodFile( + moduleName: string, + methodName: string + ): string { + const dir = resolveDirToModule(moduleName); + return resolve(dir, `${methodName}.ts`); + } + + describe.each(Object.entries(modules))('%s', (moduleName, methodsByName) => { + describe.each(Object.entries(methodsByName))( + '%s', + (methodName, signature) => { + beforeAll(() => { + // Write temp files to disk + + // Extract examples and make them runnable + const examples = extractRawExamples(signature).join('').trim(); + + // Save examples to a file to run them later in the specific tests + const dir = resolveDirToModule(moduleName); + mkdirSync(dir, { recursive: true }); + + const path = resolvePathToMethodFile(moduleName, methodName); + const imports = [...new Set(examples.match(/faker[^\.]*(?=\.)/g))]; + writeFileSync( + path, + `import { ${imports.join( + ', ' + )} } from '../../../../../src';\n\n${examples}` + ); + }); + + it('verify @example tag', async () => { + // Extract the examples + const examples = extractRawExamples(signature).join('').trim(); + + expect( + examples, + `${moduleName}.${methodName} to have examples` + ).not.toBe(''); + + // Grab path to example file + const path = resolvePathToMethodFile(moduleName, methodName); + + // Executing the examples should not throw + await expect(import(`${path}?scope=example`)).resolves.toBeDefined(); + }); + + // This only checks whether the whole method is deprecated or not + // It does not check whether the method is deprecated for a specific set of arguments + it('verify @deprecated tag', async () => { + // Grab path to example file + const path = resolvePathToMethodFile(moduleName, methodName); + + const consoleWarnSpy = vi.spyOn(console, 'warn'); + + // Run the examples + await import(`${path}?scope=deprecated`); + + // Verify that deprecated methods log a warning + const deprecatedFlag = extractDeprecated(signature) !== undefined; + if (deprecatedFlag) { + expect(consoleWarnSpy).toHaveBeenCalled(); + expect( + extractTagContent('@deprecated', signature).join(''), + '@deprecated tag without message' + ).not.toBe(''); + } else { + expect(consoleWarnSpy).not.toHaveBeenCalled(); + } + }); + + it('verify @param tags', () => { + analyzeSignature( + signature, + moduleName, + methodName + ).parameters.forEach((param) => { + const { name, description } = param; + const plainDescription = description.replace(/<[^>]+>/g, '').trim(); + expect( + plainDescription, + `Expect param ${name} to have a description` + ).not.toBe(MISSING_DESCRIPTION); + }); + }); + + it('verify @see tags', () => { + extractSeeAlsos(signature).forEach((link) => { + if (link.startsWith('faker.')) { + // Expected @see faker.xxx.yyy() + expect(link, 'Expect method reference to contain ()').toContain( + '(' + ); + expect(link, 'Expect method reference to contain ()').toContain( + ')' + ); + } + }); + }); + + it('verify @since tag', () => { + const since = extractSince(signature); + expect(since, '@since to be present').toBeTruthy(); + expect(since, '@since to be a valid semver').toSatisfy( + validator.isSemVer + ); + }); + } + ); + }); +});