diff options
| author | ST-DDT <[email protected]> | 2023-04-20 20:22:26 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-04-20 20:22:26 +0200 |
| commit | a001ac5cf284d3027b9726f1f1f5f3fa14bc7bf4 (patch) | |
| tree | 16282634fd7ef8954e8d2f4557d7e72dea735423 /test | |
| parent | 2098b4d20e9604c95ab24f2f89452a11ae5f49d7 (diff) | |
| download | faker-a001ac5cf284d3027b9726f1f1f5f3fa14bc7bf4.tar.xz faker-a001ac5cf284d3027b9726f1f1f5f3fa14bc7bf4.zip | |
docs: check api references (#2024)
Diffstat (limited to 'test')
| -rw-r--r-- | test/scripts/apidoc/signature.debug.ts | 6 | ||||
| -rw-r--r-- | test/scripts/apidoc/signature.spec.ts | 10 | ||||
| -rw-r--r-- | test/scripts/apidoc/utils.ts | 15 | ||||
| -rw-r--r-- | test/scripts/apidoc/verify-jsdoc-tags.spec.ts | 258 |
4 files changed, 179 insertions, 110 deletions
diff --git a/test/scripts/apidoc/signature.debug.ts b/test/scripts/apidoc/signature.debug.ts index 0076dfa7..347b8235 100644 --- a/test/scripts/apidoc/signature.debug.ts +++ b/test/scripts/apidoc/signature.debug.ts @@ -2,10 +2,8 @@ * This file exists, because vitest doesn't allow me to debug code outside of src and test. * And it's easier to test these features independently from the main project. */ -import { - analyzeSignature, - initMarkdownRenderer, -} from '../../../scripts/apidoc/signature'; +import { initMarkdownRenderer } from '../../../scripts/apidoc/markdown'; +import { analyzeSignature } from '../../../scripts/apidoc/signature'; import { loadExampleMethods } from './utils'; /* Run with `pnpm tsx test/scripts/apidoc/signature.debug.ts` */ diff --git a/test/scripts/apidoc/signature.spec.ts b/test/scripts/apidoc/signature.spec.ts index 3a48c29b..e80d78e6 100644 --- a/test/scripts/apidoc/signature.spec.ts +++ b/test/scripts/apidoc/signature.spec.ts @@ -1,8 +1,6 @@ import { beforeAll, describe, expect, it } from 'vitest'; -import { - analyzeSignature, - initMarkdownRenderer, -} from '../../../scripts/apidoc/signature'; +import { initMarkdownRenderer } from '../../../scripts/apidoc/markdown'; +import { analyzeSignature } from '../../../scripts/apidoc/signature'; import { SignatureTest } from './signature.example'; import { loadExampleMethods } from './utils'; @@ -10,9 +8,7 @@ describe('signature', () => { describe('analyzeSignature()', () => { const methods = loadExampleMethods(); - beforeAll(async () => { - await initMarkdownRenderer(); - }); + beforeAll(initMarkdownRenderer); it('dummy dependency to rerun the test if the example changes', () => { expect(new SignatureTest()).toBeTruthy(); diff --git a/test/scripts/apidoc/utils.ts b/test/scripts/apidoc/utils.ts index 809240c8..281fb2ef 100644 --- a/test/scripts/apidoc/utils.ts +++ b/test/scripts/apidoc/utils.ts @@ -1,4 +1,8 @@ -import type { SignatureReflection, TypeDocOptions } from 'typedoc'; +import type { + DeclarationReflection, + SignatureReflection, + TypeDocOptions, +} from 'typedoc'; import { loadProject, selectApiMethodSignatures, @@ -12,12 +16,15 @@ import { mapByName } from '../../../scripts/apidoc/utils'; export function loadProjectModules( options?: Partial<TypeDocOptions>, includeTestModules = false -): Record<string, Record<string, SignatureReflection>> { +): Record< + string, + [DeclarationReflection, Record<string, SignatureReflection>] +> { const [, project] = loadProject(options); const modules = selectApiModules(project, includeTestModules); - return mapByName(modules, selectApiMethodSignatures); + return mapByName(modules, (m) => [m, selectApiMethodSignatures(m)]); } /** @@ -30,5 +37,5 @@ export function loadExampleMethods(): Record<string, SignatureReflection> { tsconfig: 'test/scripts/apidoc/tsconfig.json', }, true - )['SignatureTest']; + )['SignatureTest'][1]; } diff --git a/test/scripts/apidoc/verify-jsdoc-tags.spec.ts b/test/scripts/apidoc/verify-jsdoc-tags.spec.ts index 68692fab..1e8fc55e 100644 --- a/test/scripts/apidoc/verify-jsdoc-tags.spec.ts +++ b/test/scripts/apidoc/verify-jsdoc-tags.spec.ts @@ -1,18 +1,18 @@ -import { mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { existsSync, mkdirSync, rmSync, 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 { initMarkdownRenderer } from '../../../scripts/apidoc/markdown'; +import { analyzeSignature } from '../../../scripts/apidoc/signature'; import { extractDeprecated, + extractDescription, + extractModuleFieldName, extractRawExamples, extractSeeAlsos, extractSince, extractTagContent, + MISSING_DESCRIPTION, } from '../../../scripts/apidoc/typedoc'; import { loadProjectModules } from './utils'; @@ -26,7 +26,9 @@ const tempDir = resolve(__dirname, 'temp'); afterAll(() => { // Remove temp folder - rmSync(tempDir, { recursive: true }); + if (existsSync(tempDir)) { + rmSync(tempDir, { recursive: true }); + } }); describe('verify JSDoc tags', () => { @@ -44,107 +46,173 @@ describe('verify JSDoc tags', () => { 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}` - ); + const allowedReferences = new Set( + Object.values(modules).reduce((acc, [module, methods]) => { + const moduleFieldName = extractModuleFieldName(module); + return [ + ...acc, + ...Object.keys(methods).map( + (methodName) => `faker.${moduleFieldName}.${methodName}` + ), + ]; + }, [] as string[]) + ); + const allowedLinks = new Set( + Object.values(modules).reduce((acc, [module, methods]) => { + const moduleFieldName = extractModuleFieldName(module); + return [ + ...acc, + `/api/${moduleFieldName}.html`, + ...Object.keys(methods).map( + (methodName) => + `/api/${moduleFieldName}.html#${methodName.toLowerCase()}` + ), + ]; + }, [] as string[]) + ); + + function assertDescription(description: string, isHtml: boolean): void { + const linkRegexp = isHtml + ? /(href)="([^"]+)"/g + : /\[([^\]]+)\]\(([^)]+)\)/g; + const links = [...description.matchAll(linkRegexp)].map((m) => m[2]); + + for (const link of links) { + if (!isHtml) { + expect(link).toMatch(/^https?:\/\//); + expect(link).toSatisfy(validator.isURL); + } + + if ( + isHtml ? link.startsWith('/api/') : link.includes('fakerjs.dev/api/') + ) { + expect(allowedLinks, `${link} to point to a valid target`).toContain( + link.replace(/.*fakerjs.dev\//, '/') + ); + } + } + } + + describe.each(Object.entries(modules))( + '%s', + (moduleName, [module, methodsByName]) => { + describe('verify module', () => { + it('verify description', () => { + const description = extractDescription(module); + assertDescription(description, false); }); + }); + + 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(); + it('verify description', () => { + const description = extractDescription(signature); + assertDescription(description, false); + }); - expect( - examples, - `${moduleName}.${methodName} to have examples` - ).not.toBe(''); + it('verify @example tag', async () => { + // Extract the examples + const examples = extractRawExamples(signature).join('').trim(); - // Grab path to example file - const path = resolvePathToMethodFile(moduleName, methodName); + expect( + examples, + `${moduleName}.${methodName} to have examples` + ).not.toBe(''); - // Executing the examples should not throw - await expect(import(`${path}?scope=example`)).resolves.toBeDefined(); - }); + // Grab path to example file + const path = resolvePathToMethodFile(moduleName, methodName); - // 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); + // Executing the examples should not throw + await expect( + import(`${path}?scope=example`) + ).resolves.toBeDefined(); + }); - const consoleWarnSpy = vi.spyOn(console, 'warn'); + // 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); - // Run the examples - await import(`${path}?scope=deprecated`); + const consoleWarnSpy = vi.spyOn(console, 'warn'); - // 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(); - } - }); + // Run the examples + await import(`${path}?scope=deprecated`); - it('verify @param tags', () => { - analyzeSignature(signature, '', methodName).parameters.forEach( - (param) => { - const { name, description } = param; - const plainDescription = description - .replace(/<[^>]+>/g, '') - .trim(); + // Verify that deprecated methods log a warning + const deprecatedFlag = extractDeprecated(signature) !== undefined; + if (deprecatedFlag) { + expect(consoleWarnSpy).toHaveBeenCalled(); expect( - plainDescription, - `Expect param ${name} to have a description` - ).not.toBe(MISSING_DESCRIPTION); + extractTagContent('@deprecated', signature).join(''), + '@deprecated tag without message' + ).not.toBe(''); + } else { + expect(consoleWarnSpy).not.toHaveBeenCalled(); } - ); - }); + }); - 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 @param tags', () => { + analyzeSignature(signature, '', 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); + assertDescription(description, true); + } + ); }); - }); - 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 - ); - }); - } - ); - }); + 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( + ')' + ); + expect(allowedReferences).toContain(link.replace(/\(.*/, '')); + } + }); + }); + + 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 + ); + }); + } + ); + } + ); }); |
