diff --git a/src/data/article.test.ts b/src/data/article.test.ts
index 81483fb..534a8da 100644
--- a/src/data/article.test.ts
+++ b/src/data/article.test.ts
@@ -156,6 +156,20 @@ _System overview._`;
`);
});
+test("converts asterisk-emphasized image captions to esa figures", () => {
+ const markdown = `
+*Request flow.*`;
+
+ expect(
+ toEsaMarkdown(markdown, {
+ canonicalUrl: "https://furedea.com/ja/blog/article-publishing/",
+ }),
+ ).toContain(`
+
+Request flow.
+`);
+});
+
test("preserves Zenn extension examples inside fenced code blocks", () => {
const markdown = `\`\`\`markdown
:::details Example
diff --git a/src/data/article.ts b/src/data/article.ts
index 98e9b5e..e0f5890 100644
--- a/src/data/article.ts
+++ b/src/data/article.ts
@@ -183,18 +183,25 @@ function restoreFencedBlocks(markdown: string, fencedBlocks: string[]): string {
}
function convertCaptions(markdown: string): string {
- return markdown.replace(
- /^!\[([^\]]*)\]\(([^)\s]+)\)\n([_*])(.+)\3$/gmu,
- (_match, ...captures: string[]) => {
- const [alt, url, , caption] = captures;
- return [
- "",
- `
`,
- `${escapeHtml(caption)}`,
- "",
- ].join("\n");
- },
- );
+ const captionedImages =
+ /^!\[(?[^\]]*)\]\((?[^)\s]+)\)\n(?[_*])(?.+)\k$/gmu;
+ let converted = "";
+ let copiedUntil = 0;
+ for (const match of markdown.matchAll(captionedImages)) {
+ const { alt = "", url = "", caption = "" } = match.groups ?? {};
+ converted += markdown.slice(copiedUntil, match.index) + captionFigure(alt, url, caption);
+ copiedUntil = match.index + match[0].length;
+ }
+ return converted + markdown.slice(copiedUntil);
+}
+
+function captionFigure(alt: string, url: string, caption: string): string {
+ return [
+ "",
+ `
`,
+ `${escapeHtml(caption)}`,
+ "",
+ ].join("\n");
}
function replaceImageUrls(markdown: string, origin: string): string {
diff --git a/tests/performance/performance_report.test.ts b/tests/performance/performance_report.test.ts
index 0cf2585..bbbb375 100644
--- a/tests/performance/performance_report.test.ts
+++ b/tests/performance/performance_report.test.ts
@@ -79,6 +79,17 @@ describe("performance report", () => {
expect(summary).toContain("1.23 s (1.00–1.50)");
expect(summary).toContain("| 3 |");
});
+
+ test("shows the blocking time range in rounded milliseconds", () => {
+ const entry = reportEntry();
+ entry.samples = [
+ { ...entry.metrics, blockingTime: 50.4 },
+ entry.metrics,
+ { ...entry.metrics, blockingTime: 120.6 },
+ ];
+
+ expect(renderPerformanceSummary([entry])).toContain("88 ms (50–121)");
+ });
});
function reportEntry(): PerformanceReportEntry {
diff --git a/tests/performance/performance_report.ts b/tests/performance/performance_report.ts
index e881a09..07cc449 100644
--- a/tests/performance/performance_report.ts
+++ b/tests/performance/performance_report.ts
@@ -21,6 +21,18 @@ export type PerformanceReportEntry = {
export type PerformanceMeasurement = Omit;
+type Unit = {
+ format: (milliseconds: number) => string | number;
+ symbol: string;
+};
+
+const SECONDS: Unit = {
+ format: (milliseconds) => (milliseconds / 1_000).toFixed(2),
+ symbol: "s",
+};
+
+const MILLISECONDS: Unit = { format: Math.round, symbol: "ms" };
+
export function renderPerformanceSummary(entries: PerformanceReportEntry[]): string {
const rows = entries.toSorted(byRoute).map(renderRow);
return [
@@ -68,9 +80,9 @@ function renderRow(entry: PerformanceReportEntry): string {
(value) => value.toFixed(3),
),
unitWithRange(
- { median: metrics.blockingTime, samples: samples.map(({ blockingTime }) => blockingTime) },
- "ms",
- Math.round,
+ metrics.blockingTime,
+ samples.map(({ blockingTime }) => blockingTime),
+ MILLISECONDS,
),
`${samples.length} |`,
].join(" | ");
@@ -83,24 +95,16 @@ function statusSymbol(entry: PerformanceReportEntry): string {
return "❌";
}
-function seconds(milliseconds: number): string {
- return (milliseconds / 1_000).toFixed(2);
-}
-
function secondsWithRange(median: number, samples: number[]): string {
- return unitWithRange({ median, samples }, "s", seconds);
+ return unitWithRange(median, samples, SECONDS);
}
-function unitWithRange(
- { median, samples }: { median: number; samples: number[] },
- unit: string,
- format: (value: number) => string | number,
-): string {
+function unitWithRange(median: number, samples: number[], unit: Unit): string {
const minimum = Math.min(...samples);
const maximum = Math.max(...samples);
- const formattedMedian = format(median);
- if (minimum === maximum) return `${formattedMedian} ${unit}`;
- return `${formattedMedian} ${unit} (${format(minimum)}–${format(maximum)})`;
+ const formattedMedian = unit.format(median);
+ if (minimum === maximum) return `${formattedMedian} ${unit.symbol}`;
+ return `${formattedMedian} ${unit.symbol} (${unit.format(minimum)}–${unit.format(maximum)})`;
}
function valueWithRange(