mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
103 lines
3.5 KiB
TypeScript
103 lines
3.5 KiB
TypeScript
// @vitest-environment node
|
|||
|
|
import { globSync, readFileSync } from "node:fs";
|
||
|
|
import path from "node:path";
|
||
|
|
import { TOOLS } from "@snapotter/shared";
|
||
|
|
import { describe, expect, it } from "vitest";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* PR #520 renamed 18 tools across the app and the tool pages, but the VitePress
|
||
|
|
* sidebar kept its old labels for another two releases: 19 entries in the nav
|
||
|
|
* under names the product no longer used. One page H1 was missed too.
|
||
|
|
*
|
||
|
|
* Tool leaves in the sidebar are deliberately never translated (see
|
||
|
|
* localizeSidebar in apps/docs/.vitepress/config.mts), so checking the English
|
||
|
|
* config covers all 21 locales.
|
||
|
|
*/
|
||
|
|
|
||
|
|
const ROOT = path.resolve(__dirname, "../../..");
|
||
|
|
const NAME_BY_ID = new Map(TOOLS.map((t) => [t.id, t.name]));
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Shipped tools with no docs page. Adding a page means deleting the id here.
|
||
|
|
* Adding a tool without a page means this list grows, which is the point.
|
||
|
|
*/
|
||
|
|
const UNDOCUMENTED = new Set(["remove-gif-background", "rounded-crop"]);
|
||
|
|
|
||
|
|
interface Leaf {
|
||
|
|
label: string;
|
||
|
|
id: string;
|
||
|
|
line: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
function sidebarToolLeaves(): Leaf[] {
|
||
|
|
const src = readFileSync(path.join(ROOT, "apps/docs/.vitepress/config.mts"), "utf8");
|
||
|
|
const leaves: Leaf[] = [];
|
||
|
|
src.split("\n").forEach((line, i) => {
|
||
|
|
// The single top-nav entry sits at six spaces; sidebar leaves are deeper.
|
||
|
|
if (!/^\s{10,}\{ text: "/.test(line)) return;
|
||
|
|
const m = line.match(
|
||
|
|
/\{ text: "([^"]+)", link: "\/tools\/(?:image|video|audio|pdf|files)\/([a-z0-9-]+)" \}/,
|
||
|
|
);
|
||
|
|
if (m) leaves.push({ label: m[1], id: m[2], line: i + 1 });
|
||
|
|
});
|
||
|
|
return leaves;
|
||
|
|
}
|
||
|
|
|
||
|
|
const LEAVES = sidebarToolLeaves();
|
||
|
|
const PAGES = globSync("apps/docs/tools/*/*.md", { cwd: ROOT })
|
||
|
|
.filter((f) => !f.endsWith("conversion-presets.md"))
|
||
|
|
.sort();
|
||
|
|
|
||
|
|
describe("docs sidebar", () => {
|
||
|
|
it("found the tool leaves", () => {
|
||
|
|
expect(LEAVES.length).toBeGreaterThan(100);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("links only to tools the catalog ships", () => {
|
||
|
|
expect(LEAVES.filter((l) => !NAME_BY_ID.has(l.id)).map((l) => l.id)).toEqual([]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("labels every leaf with the tool's catalog name", () => {
|
||
|
|
const wrong = LEAVES.filter((l) => NAME_BY_ID.get(l.id) !== l.label).map(
|
||
|
|
(l) => `config.mts:${l.line} "${l.label}" should be "${NAME_BY_ID.get(l.id)}"`,
|
||
|
|
);
|
||
|
|
expect(wrong).toEqual([]);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("tool pages", () => {
|
||
|
|
it("found the pages", () => {
|
||
|
|
expect(PAGES.length).toBeGreaterThan(100);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("titles every page with the tool's catalog name", () => {
|
||
|
|
const wrong: string[] = [];
|
||
|
|
for (const file of PAGES) {
|
||
|
|
const id = path.basename(file, ".md");
|
||
|
|
const name = NAME_BY_ID.get(id);
|
||
|
|
if (!name) {
|
||
|
|
wrong.push(`${file} has no tool with id ${id}`);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const h1 = readFileSync(path.join(ROOT, file), "utf8")
|
||
|
|
.split("\n")
|
||
|
|
.find((l) => l.startsWith("# "))
|
||
|
|
?.replace(/^#\s+/, "")
|
||
|
|
.replace(/\s*\{#.*\}$/, "")
|
||
|
|
.trim();
|
||
|
|
if (h1 !== name) wrong.push(`${file} H1 "${h1}" should be "${name}"`);
|
||
|
|
}
|
||
|
|
expect(wrong).toEqual([]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("tracks exactly the tools that still have no page", () => {
|
||
|
|
const documented = new Set(PAGES.map((f) => path.basename(f, ".md")));
|
||
|
|
const missing = TOOLS.filter((t) => !documented.has(t.id))
|
||
|
|
.map((t) => t.id)
|
||
|
|
// The 83 conversion presets are covered collectively by
|
||
|
|
// apps/docs/tools/conversion-presets.md rather than one page each.
|
||
|
|
.filter((id) => !id.includes("-to-"));
|
||
|
|
expect(missing.sort()).toEqual([...UNDOCUMENTED].sort());
|
||
|
|
});
|
||
|
|
});
|