diff options
| -rw-r--r-- | .gitignore | 5 | ||||
| -rw-r--r-- | cypress/e2e/example-refresh.cy.ts | 32 | ||||
| -rw-r--r-- | docs/.vitepress/components/api-docs/format.ts | 14 | ||||
| -rw-r--r-- | docs/.vitepress/components/api-docs/method.ts | 1 | ||||
| -rw-r--r-- | docs/.vitepress/components/api-docs/method.vue | 120 | ||||
| -rw-r--r-- | docs/.vitepress/components/api-docs/refresh-button.vue | 69 | ||||
| -rw-r--r-- | docs/.vitepress/components/api-docs/refresh.svg | 1 | ||||
| -rw-r--r-- | docs/.vitepress/config.ts | 1 | ||||
| -rw-r--r-- | docs/api/.gitignore | 10 | ||||
| -rw-r--r-- | eslint.config.ts | 1 | ||||
| -rw-r--r-- | scripts/apidocs/output/page.ts | 80 | ||||
| -rw-r--r-- | test/docs/__snapshots__/format.spec.ts.snap | 19 | ||||
| -rw-r--r-- | test/docs/format.spec.ts | 69 | ||||
| -rw-r--r-- | test/scripts/apidocs/__snapshots__/page.spec.ts.snap | 97 | ||||
| -rw-r--r-- | test/scripts/apidocs/page.spec.ts | 114 | ||||
| -rw-r--r-- | tsconfig.json | 2 |
16 files changed, 618 insertions, 17 deletions
@@ -80,6 +80,11 @@ versions.json /dist /docs/.vitepress/cache /docs/.vitepress/dist +/docs/api/*.ts +!/docs/api/api-types.ts +/docs/api/*.md +!/docs/api/index.md +/docs/api/api-search-index.json /docs/public/api-diff-index.json # Faker diff --git a/cypress/e2e/example-refresh.cy.ts b/cypress/e2e/example-refresh.cy.ts new file mode 100644 index 00000000..df792eba --- /dev/null +++ b/cypress/e2e/example-refresh.cy.ts @@ -0,0 +1,32 @@ +describe('example-refresh', () => { + it('should refresh the example', () => { + // given + cy.visit('/api/faker.html#constructor'); + cy.get('.refresh').first().as('refresh'); + cy.get('@refresh').next().find('code').as('codeBlock'); + cy.get('@codeBlock').then(($el) => { + const originalCodeText = $el.text(); + + cy.get('@refresh') + .click() + .should('not.be.disabled') // stays disabled on error + .then(() => { + cy.get('@codeBlock').then(($el) => { + const newCodeText = $el.text(); + expect(newCodeText).not.to.equal(originalCodeText); + + cy.get('@refresh') + .click() + .should('not.be.disabled') // stays disabled on error + .then(() => { + cy.get('@codeBlock').then(($el) => { + const newCodeText2 = $el.text(); + expect(newCodeText2).not.to.equal(originalCodeText); + expect(newCodeText2).not.to.equal(newCodeText); + }); + }); + }); + }); + }); + }); +}); diff --git a/docs/.vitepress/components/api-docs/format.ts b/docs/.vitepress/components/api-docs/format.ts new file mode 100644 index 00000000..34de1e0c --- /dev/null +++ b/docs/.vitepress/components/api-docs/format.ts @@ -0,0 +1,14 @@ +export function formatResult(result: unknown): string { + return result === undefined + ? 'undefined' + : typeof result === 'bigint' + ? `${result}n` + : JSON.stringify(result, undefined, 2) + .replaceAll('\\r', '') + .replaceAll('<', '<') + .replaceAll( + /(^ *|: )"([^'\n]*?)"(?=,?$|: )/gm, + (_, p1, p2) => `${p1}'${p2.replace(/\\"/g, '"')}'` + ) + .replaceAll(/\n */g, ' '); +} diff --git a/docs/.vitepress/components/api-docs/method.ts b/docs/.vitepress/components/api-docs/method.ts index 4da480b8..91f99d4e 100644 --- a/docs/.vitepress/components/api-docs/method.ts +++ b/docs/.vitepress/components/api-docs/method.ts @@ -8,6 +8,7 @@ export interface ApiDocsMethod { readonly throws: string | undefined; // HTML readonly signature: string; // HTML readonly examples: string; // HTML + readonly refresh: (() => Promise<unknown[]>) | undefined; readonly seeAlsos: string[]; readonly sourcePath: string; // URL-Suffix } diff --git a/docs/.vitepress/components/api-docs/method.vue b/docs/.vitepress/components/api-docs/method.vue index 83a4100c..37b44748 100644 --- a/docs/.vitepress/components/api-docs/method.vue +++ b/docs/.vitepress/components/api-docs/method.vue @@ -1,8 +1,11 @@ <script setup lang="ts"> +import { computed, ref, useTemplateRef } from 'vue'; import { sourceBaseUrl } from '../../../api/source-base-url'; import { slugify } from '../../shared/utils/slugify'; +import { formatResult } from './format'; import type { ApiDocsMethod } from './method'; import MethodParameters from './method-parameters.vue'; +import RefreshButton from './refresh-button.vue'; const { method } = defineProps<{ method: ApiDocsMethod }>(); const { @@ -14,10 +17,113 @@ const { throws, signature, examples, + refresh, seeAlsos, sourcePath, } = method; +const code = useTemplateRef('code'); +const codeBlock = computed(() => code.value?.querySelector('div pre code')); +const codeLines = ref<Element[]>(); + +function initRefresh(): Element[] { + if (codeBlock.value == null) { + return []; + } + const domLines = codeBlock.value.querySelectorAll('.line'); + let lineIndex = 0; + const result: Element[] = []; + while (lineIndex < domLines.length) { + // Skip empty and preparatory lines (no '^faker.' invocation) + if ( + domLines[lineIndex]?.children.length === 0 || + !/^\w*faker\w*\./i.test(domLines[lineIndex]?.textContent ?? '') + ) { + lineIndex++; + continue; + } + + // Skip to end of the invocation (if multiline) + while ( + domLines[lineIndex] != null && + !/^([^ ].*)?\)(\.\w+)?;? ?(\/\/|$)/.test( + domLines[lineIndex]?.textContent ?? '' + ) + ) { + lineIndex++; + } + + if (lineIndex >= domLines.length) { + break; + } + + const domLine = domLines[lineIndex]; + result.push(domLine); + lineIndex++; + + // Purge old results + if (domLine.lastElementChild?.textContent?.startsWith('//')) { + // Inline comments + domLine.lastElementChild.remove(); + } else { + // Multiline comments + while (domLines[lineIndex]?.children[0]?.textContent?.startsWith('//')) { + domLines[lineIndex].previousSibling?.remove(); // newline + domLines[lineIndex].remove(); // comment + lineIndex++; + } + } + + // Add space between invocation and comment (if missing) + const lastElementChild = domLine.lastElementChild; + if ( + lastElementChild != null && + !lastElementChild.textContent?.endsWith(' ') + ) { + lastElementChild.textContent += ' '; + } + } + + return result; +} + +async function onRefresh(): Promise<void> { + if (refresh != null && codeBlock.value != null) { + codeLines.value ??= initRefresh(); + + const results = await refresh(); + + // Remove old comments + codeBlock.value + .querySelectorAll('.comment-delete-marker') + .forEach((el) => el.remove()); + + // Insert new comments + for (let i = 0; i < results.length; i++) { + const result = results[i]; + const domLine = codeLines.value[i]; + const prettyResult = formatResult(result); + const resultLines = prettyResult.split('\\n'); + + if (resultLines.length === 1) { + domLine.insertAdjacentHTML('beforeend', newCommentSpan(resultLines[0])); + } else { + for (const line of resultLines.reverse()) { + domLine.insertAdjacentHTML('afterend', newCommentLine(line)); + } + } + } + } +} + +function newCommentLine(content: string): string { + return `<span class="line comment-delete-marker">\n${newCommentSpan(content)}</span>`; +} + +function newCommentSpan(content: string): string { + return `<span class="comment-delete-marker" style="--shiki-light:#6A737D;--shiki-dark:#6A737D">// ${content}</span>`; +} + function seeAlsoToUrl(see: string): string { const [, module, methodName] = see.replace(/\(.*/, '').split('\.'); @@ -51,8 +157,14 @@ function seeAlsoToUrl(see: string): string { <div v-html="signature" /> - <h3>Examples</h3> - <div v-html="examples" /> + <h3 class="inline">Examples</h3> + <RefreshButton + class="refresh" + v-if="refresh != null" + style="margin-left: 0.5em" + :refresh="onRefresh" + /> + <div ref="code" v-html="examples" /> <div v-if="seeAlsos.length > 0"> <h3>See Also</h3> @@ -107,4 +219,8 @@ svg.source-link-icon { display: inline; margin-left: 0.3em; } + +h3.inline { + display: inline-block; +} </style> diff --git a/docs/.vitepress/components/api-docs/refresh-button.vue b/docs/.vitepress/components/api-docs/refresh-button.vue new file mode 100644 index 00000000..c2210eab --- /dev/null +++ b/docs/.vitepress/components/api-docs/refresh-button.vue @@ -0,0 +1,69 @@ +<script setup lang="ts"> +import { ref } from 'vue'; + +// This should probably use emit instead, but emit cannot be awaited +const { refresh } = defineProps<{ refresh: () => Promise<void> }>(); + +const spinning = ref(false); + +async function onRefresh() { + spinning.value = true; + await Promise.all([refresh(), delay(100)]); + spinning.value = false; +} + +// Extra delay to make the spinning effect more visible +// Some examples barely/don't change, so the spinning is the only visible effect +function delay(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} +</script> + +<template> + <button + class="refresh" + title="Refresh Examples" + :disabled="spinning" + @click="onRefresh" + > + <div :class="{ spinning: spinning }" /> + </button> +</template> + +<style scoped> +button.refresh { + border: 1px solid var(--vp-code-copy-code-border-color); + border-radius: 4px; + width: 40px; + height: 40px; + font-size: 25px; + vertical-align: middle; +} + +button.refresh div { + background-image: url('refresh.svg'); + background-position: 50%; + background-size: 20px; + background-repeat: no-repeat; + width: 100%; + height: 100%; +} + +button.refresh:hover { + background-color: var(--vp-code-copy-code-bg); + opacity: 1; +} + +div.spinning { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} +</style> diff --git a/docs/.vitepress/components/api-docs/refresh.svg b/docs/.vitepress/components/api-docs/refresh.svg new file mode 100644 index 00000000..8320a2b2 --- /dev/null +++ b/docs/.vitepress/components/api-docs/refresh.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M20 12a8 8 0 0 1-6.44 7.85 8 8 0 0 1-8.95-4.79 8 8 0 0 1 2.94-9.71 8 8 0 0 1 10.1 1" style="fill:none;stroke:rgba(128,128,128,1);stroke-width:2"/><path d="m19.65 4.25-4.04 4.03 5.5 1.47z" style="fill:rgba(128,128,128,1)"/></svg>
\ No newline at end of file diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 746803ad..4fcc469e 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -133,6 +133,7 @@ async function enableFaker() { e.g. 'faker.food.description()' or 'fakerZH_CN.person.firstName()' For other languages please refer to https://fakerjs.dev/guide/localization.html#available-locales For a full list of all methods please refer to https://fakerjs.dev/api/\`, logStyle); + enableFaker = () => imported; // Init only once return imported; } `, diff --git a/docs/api/.gitignore b/docs/api/.gitignore deleted file mode 100644 index 47b11a83..00000000 --- a/docs/api/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Markdown -*.md -!index.md - -# TypeScript -*.ts -!api-types.ts - -# JSON -*.json diff --git a/eslint.config.ts b/eslint.config.ts index 812c8a92..8c980b2c 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -24,6 +24,7 @@ const config: ReturnType<typeof tseslint.config> = tseslint.config( '.github/workflows/commentCodeGeneration.ts', '.prettierrc.js', 'docs/.vitepress/components/shims.d.ts', + 'docs/.vitepress/components/api-docs/format.ts', 'docs/.vitepress/shared/utils/slugify.ts', 'docs/.vitepress/theme/index.ts', 'eslint.config.js', diff --git a/scripts/apidocs/output/page.ts b/scripts/apidocs/output/page.ts index c00ba4d2..a9570309 100644 --- a/scripts/apidocs/output/page.ts +++ b/scripts/apidocs/output/page.ts @@ -33,7 +33,7 @@ export async function writePages(pages: RawApiDocsPage[]): Promise<void> { async function writePage(page: RawApiDocsPage): Promise<void> { try { await writePageMarkdown(page); - await writePageJsonData(page); + await writePageData(page); } catch (error) { throw new Error(`Error writing page ${page.title}`, { cause: error }); } @@ -51,7 +51,7 @@ async function writePageMarkdown(page: RawApiDocsPage): Promise<void> { let content = ` <script setup> import ApiDocsMethod from '../.vitepress/components/api-docs/method.vue'; - import ${camelTitle} from './${camelTitle}.json'; + import ${camelTitle} from './${camelTitle}.ts'; </script> <!-- This file is automatically generated. --> @@ -98,16 +98,33 @@ async function writePageMarkdown(page: RawApiDocsPage): Promise<void> { * * @param page The page to write. */ -async function writePageJsonData(page: RawApiDocsPage): Promise<void> { +async function writePageData(page: RawApiDocsPage): Promise<void> { const { camelTitle, methods } = page; const pageData: Record<string, ApiDocsMethod> = Object.fromEntries( await Promise.all( methods.map(async (method) => [method.name, await toMethodData(method)]) ) ); - const content = JSON.stringify(pageData, null, 2); - writeFileSync(resolve(FILE_PATH_API_DOCS, `${camelTitle}.json`), content); + const refreshFunctions: Record<string, string> = Object.fromEntries( + await Promise.all( + methods.map(async (method) => [ + method.name, + await toRefreshFunction(method), + ]) + ) + ); + + const content = + `export default ${JSON.stringify(pageData, undefined, 2)}`.replaceAll( + /"refresh-([^"-]+)-placeholder"/g, + (_, name) => refreshFunctions[name] + ); + + writeFileSync( + resolve(FILE_PATH_API_DOCS, `${camelTitle}.ts`), + await formatTypescript(content) + ); } const defaultCommentRegex = /\s+Defaults to `([^`]+)`\..*/; @@ -130,6 +147,12 @@ async function toMethodData(method: RawApiDocsMethod): Promise<ApiDocsMethod> { let formattedSignature = await formatTypescript(signature); formattedSignature = formattedSignature.trim(); + // eslint-disable-next-line @typescript-eslint/require-await + const refresh = async () => ['refresh', name, 'placeholder']; + // This is a placeholder to be replaced by the actual refresh function code + // If we put the actual code here, it would be a string and not executable + refresh.toJSON = () => `refresh-${name}-placeholder`; + /* Target order, omitted to improve diff to old files return { name, @@ -167,6 +190,7 @@ async function toMethodData(method: RawApiDocsMethod): Promise<ApiDocsMethod> { returns: returns.text, signature: codeToHtml(formattedSignature), examples: codeToHtml(examples.join('\n')), + refresh, deprecated: mdToHtml(deprecated), seeAlsos: seeAlsos.map((seeAlso) => mdToHtml(seeAlso, true)), }; @@ -175,3 +199,49 @@ async function toMethodData(method: RawApiDocsMethod): Promise<ApiDocsMethod> { export function extractSummaryDefault(description: string): string | undefined { return defaultCommentRegex.exec(description)?.[1]; } + +export async function toRefreshFunction( + method: RawApiDocsMethod +): Promise<string> { + const { name, signatures } = method; + const signatureData = required(signatures.at(-1), 'method signature'); + const { examples } = signatureData; + + const exampleCode = examples.join('\n'); + if (!/^\w*faker\w*\./im.test(exampleCode)) { + // No recordable faker calls in examples + return 'undefined'; + } + + const exampleLines = exampleCode + .replaceAll(/ ?\/\/.*$/gm, '') // Remove comments + .replaceAll(/^import .*$/gm, '') // Remove imports + .replaceAll( + // record results of faker calls + /^(\w*faker\w*\..+(?:(?:.|\n..)*\n[^ ])?\)(?:\.\w+)?);?$/gim, + `try { result.push($1); } catch (error: unknown) { result.push(error instanceof Error ? error.name : 'Error'); }\n` + ); + + const fullMethod = `async (): Promise<unknown[]> => { +await enableFaker(); +faker.seed(); +faker.setDefaultRefDate(); +const result: unknown[] = []; + +${exampleLines} + +return result; +}`; + try { + const formattedMethod = await formatTypescript(fullMethod); + return formattedMethod.replace(/;\s+$/, ''); // Remove trailing semicolon + } catch (error: unknown) { + console.error( + 'Failed to format refresh function for', + name, + fullMethod, + error + ); + return 'undefined'; + } +} diff --git a/test/docs/__snapshots__/format.spec.ts.snap b/test/docs/__snapshots__/format.spec.ts.snap new file mode 100644 index 00000000..36679523 --- /dev/null +++ b/test/docs/__snapshots__/format.spec.ts.snap @@ -0,0 +1,19 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`formatResult > should format Date 1`] = `"'2025-01-01T00:00:00.000Z'"`; + +exports[`formatResult > should format array 1`] = `"[ 1, '2' ]"`; + +exports[`formatResult > should format bigint 1`] = `"135464154865415n"`; + +exports[`formatResult > should format number 1`] = `"123"`; + +exports[`formatResult > should format object 1`] = `"{ 'a': 1, 'b': '2' }"`; + +exports[`formatResult > should format string 1`] = `"'a simple string'"`; + +exports[`formatResult > should format string with new lines 1`] = `"'string\\nwith\\nnew\\nlines'"`; + +exports[`formatResult > should format string with special characters 1`] = `"'string with "special" characters'"`; + +exports[`formatResult > should format undefined 1`] = `"undefined"`; diff --git a/test/docs/format.spec.ts b/test/docs/format.spec.ts new file mode 100644 index 00000000..bc4a0d66 --- /dev/null +++ b/test/docs/format.spec.ts @@ -0,0 +1,69 @@ +import { describe, expect, it } from 'vitest'; +import { formatResult } from '../../docs/.vitepress/components/api-docs/format'; + +describe('formatResult', () => { + it('should format undefined', () => { + const value = undefined; + const actual = formatResult(value); + + expect(actual).toBeTypeOf('string'); + expect(actual).toBe('undefined'); + expect(actual).toMatchSnapshot(); + }); + + it('should format bigint', () => { + const actual = formatResult(135464154865415n); + + expect(actual).toBeTypeOf('string'); + expect(actual).toMatchSnapshot(); + }); + + it('should format object', () => { + const actual = formatResult({ a: 1, b: '2' }); + + expect(actual).toBeTypeOf('string'); + expect(actual).toMatchSnapshot(); + }); + + it('should format array', () => { + const actual = formatResult([1, '2']); + + expect(actual).toBeTypeOf('string'); + expect(actual).toMatchSnapshot(); + }); + + it('should format string', () => { + const actual = formatResult('a simple string'); + + expect(actual).toBeTypeOf('string'); + expect(actual).toMatchSnapshot(); + }); + + it('should format string with special characters', () => { + const actual = formatResult('string with "special" characters'); + + expect(actual).toBeTypeOf('string'); + expect(actual).toMatchSnapshot(); + }); + + it('should format string with new lines', () => { + const actual = formatResult('string\nwith\nnew\nlines'); + + expect(actual).toBeTypeOf('string'); + expect(actual).toMatchSnapshot(); + }); + + it('should format number', () => { + const actual = formatResult(123); + + expect(actual).toBeTypeOf('string'); + expect(actual).toMatchSnapshot(); + }); + + it('should format Date', () => { + const actual = formatResult(new Date(Date.UTC(2025, 0, 1))); + + expect(actual).toBeTypeOf('string'); + expect(actual).toMatchSnapshot(); + }); +}); diff --git a/test/scripts/apidocs/__snapshots__/page.spec.ts.snap b/test/scripts/apidocs/__snapshots__/page.spec.ts.snap new file mode 100644 index 00000000..4824c101 --- /dev/null +++ b/test/scripts/apidocs/__snapshots__/page.spec.ts.snap @@ -0,0 +1,97 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`toRefreshFunction > should handle multiline calls 1`] = ` +"async (): Promise<unknown[]> => { + await enableFaker(); + faker.seed(); + faker.setDefaultRefDate(); + const result: unknown[] = []; + + try { + result.push( + faker.number.int({ + min: 1, + max: 10, + }) + ); + } catch (error: unknown) { + result.push(error instanceof Error ? error.name : 'Error'); + } + + return result; +}" +`; + +exports[`toRefreshFunction > should handle multiple calls 1`] = ` +"async (): Promise<unknown[]> => { + await enableFaker(); + faker.seed(); + faker.setDefaultRefDate(); + const result: unknown[] = []; + + try { + result.push(faker.number.int()); + } catch (error: unknown) { + result.push(error instanceof Error ? error.name : 'Error'); + } + + try { + result.push(faker.number.int()); + } catch (error: unknown) { + result.push(error instanceof Error ? error.name : 'Error'); + } + + return result; +}" +`; + +exports[`toRefreshFunction > should handle properties after calls 1`] = ` +"async (): Promise<unknown[]> => { + await enableFaker(); + faker.seed(); + faker.setDefaultRefDate(); + const result: unknown[] = []; + + try { + result.push(faker.airline.airport().name); + } catch (error: unknown) { + result.push(error instanceof Error ? error.name : 'Error'); + } + + return result; +}" +`; + +exports[`toRefreshFunction > should handle single line calls with semicolon 1`] = ` +"async (): Promise<unknown[]> => { + await enableFaker(); + faker.seed(); + faker.setDefaultRefDate(); + const result: unknown[] = []; + + try { + result.push(faker.number.int()); + } catch (error: unknown) { + result.push(error instanceof Error ? error.name : 'Error'); + } + + return result; +}" +`; + +exports[`toRefreshFunction > should handle single line calls without semicolon 1`] = ` +"async (): Promise<unknown[]> => { + await enableFaker(); + faker.seed(); + faker.setDefaultRefDate(); + const result: unknown[] = []; + + try { + result.push(faker.number.int()); + } catch (error: unknown) { + result.push(error instanceof Error ? error.name : 'Error'); + } + + return result; +}" +`; diff --git a/test/scripts/apidocs/page.spec.ts b/test/scripts/apidocs/page.spec.ts new file mode 100644 index 00000000..00fd4da2 --- /dev/null +++ b/test/scripts/apidocs/page.spec.ts @@ -0,0 +1,114 @@ +import { describe, expect, it } from 'vitest'; +import { toRefreshFunction } from '../../../scripts/apidocs/output/page'; +import type { RawApiDocsMethod } from '../../../scripts/apidocs/processing/method'; +import type { RawApiDocsSignature } from '../../../scripts/apidocs/processing/signature'; + +function newTestMethod( + signature: Partial<RawApiDocsSignature> +): RawApiDocsMethod { + return { + name: 'test', + signatures: [ + { + deprecated: 'deprecated', + description: 'description', + since: 'since', + parameters: [], + returns: { + type: 'simple', + text: 'returns', + }, + throws: [], + signature: 'signature', + examples: [], + seeAlsos: [], + ...signature, + }, + ], + source: { + filePath: 'test/page.spec.ts', + line: 1, + column: 1, + }, + }; +} + +describe('toRefreshFunction', () => { + it("should return 'undefined' when there are no faker calls", async () => { + // given + const method = newTestMethod({ + examples: ['const a = 1;'], + }); + + // when + const result = await toRefreshFunction(method); + + // then + expect(result).toBe('undefined'); + }); + + it('should handle single line calls with semicolon', async () => { + // given + const method = newTestMethod({ + examples: ['faker.number.int(); // 834135'], + }); + + // when + const result = await toRefreshFunction(method); + + // then + expect(result).toMatchSnapshot(); + }); + + it('should handle single line calls without semicolon', async () => { + // given + const method = newTestMethod({ + examples: ['faker.number.int() // 834135'], + }); + + // when + const result = await toRefreshFunction(method); + + // then + expect(result).toMatchSnapshot(); + }); + + it('should handle multiple calls', async () => { + // given + const method = newTestMethod({ + examples: ['faker.number.int()', 'faker.number.int()'], + }); + + // when + const result = await toRefreshFunction(method); + + // then + expect(result).toMatchSnapshot(); + }); + + it('should handle multiline calls', async () => { + // given + const method = newTestMethod({ + examples: 'faker.number.int({\n min: 1,\n max: 10\n})'.split('\n'), + }); + + // when + const result = await toRefreshFunction(method); + + // then + expect(result).toMatchSnapshot(); + }); + + it('should handle properties after calls', async () => { + // given + const method = newTestMethod({ + examples: ['faker.airline.airport().name'], + }); + + // when + const result = await toRefreshFunction(method); + + // then + expect(result).toMatchSnapshot(); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index c8ffb862..388a8f79 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,8 @@ "exclude": [ "node_modules", "dist", + // Ignore the generated API documentation + "docs/api", // required for the signature related tests on macOS #2280 "test/scripts/apidocs/temp" ] |
