Skip to content

Commit

Permalink
docs(api): add refresh button to examples (#3301)
Browse files Browse the repository at this point in the history
* docs(api): add refresh button to examples

* chore: improve button behavior slightly

* chore: improve output format

* chore: ignore examples without recordable results

* temp

* chore: use svg button

* chore: use json5 format for test

* chore: simplify result formatting

* test: add formatting tests

* test: add e2e refresh test

* test: use static test values

* chore: fix regex

* chore: simplify refresh placeholder

* Update cypress/e2e/example-refresh.cy.ts

* fix: handle property after function call

* Apply suggestions from code review

Co-authored-by: Shinigami <chrissi92@hotmail.de>

* Apply suggestions from code review

Co-authored-by: Shinigami <chrissi92@hotmail.de>

* Apply suggestions from code review

Co-authored-by: Shinigami <chrissi92@hotmail.de>

* chore: format

* chore: add comment

---------

Co-authored-by: Shinigami <chrissi92@hotmail.de>
  • Loading branch information
ST-DDT and Shinigami92 authored Dec 28, 2024
1 parent 817f8a0 commit e6d27a3
Show file tree
Hide file tree
Showing 16 changed files with 618 additions and 17 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 32 additions & 0 deletions cypress/e2e/example-refresh.cy.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
});
});
});
});
14 changes: 14 additions & 0 deletions docs/.vitepress/components/api-docs/format.ts
Original file line number Diff line number Diff line change
@@ -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('<', '&lt;')
.replaceAll(
/(^ *|: )"([^'\n]*?)"(?=,?$|: )/gm,
(_, p1, p2) => `${p1}'${p2.replace(/\\"/g, '"')}'`
)
.replaceAll(/\n */g, ' ');
}
1 change: 1 addition & 0 deletions docs/.vitepress/components/api-docs/method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
120 changes: 118 additions & 2 deletions docs/.vitepress/components/api-docs/method.vue
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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('\.');
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -107,4 +219,8 @@ svg.source-link-icon {
display: inline;
margin-left: 0.3em;
}
h3.inline {
display: inline-block;
}
</style>
69 changes: 69 additions & 0 deletions docs/.vitepress/components/api-docs/refresh-button.vue
Original file line number Diff line number Diff line change
@@ -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>
1 change: 1 addition & 0 deletions docs/.vitepress/components/api-docs/refresh.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
`,
Expand Down
10 changes: 0 additions & 10 deletions docs/api/.gitignore

This file was deleted.

1 change: 1 addition & 0 deletions eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading

0 comments on commit e6d27a3

Please sign in to comment.