aboutsummaryrefslogtreecommitdiff
path: root/test/scripts
diff options
context:
space:
mode:
authorST-DDT <[email protected]>2023-04-20 20:22:26 +0200
committerGitHub <[email protected]>2023-04-20 20:22:26 +0200
commita001ac5cf284d3027b9726f1f1f5f3fa14bc7bf4 (patch)
tree16282634fd7ef8954e8d2f4557d7e72dea735423 /test/scripts
parent2098b4d20e9604c95ab24f2f89452a11ae5f49d7 (diff)
downloadfaker-a001ac5cf284d3027b9726f1f1f5f3fa14bc7bf4.tar.xz
faker-a001ac5cf284d3027b9726f1f1f5f3fa14bc7bf4.zip
docs: check api references (#2024)
Diffstat (limited to 'test/scripts')
-rw-r--r--test/scripts/apidoc/signature.debug.ts6
-rw-r--r--test/scripts/apidoc/signature.spec.ts10
-rw-r--r--test/scripts/apidoc/utils.ts15
-rw-r--r--test/scripts/apidoc/verify-jsdoc-tags.spec.ts258
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
+ );
+ });
+ }
+ );
+ }
+ );
});