/** * @copyright NHCarrigan * @license Naomi's Public License * @author Naomi Carrigan */ import { readFile } from "node:fs/promises"; import { readdir } from "node:fs/promises"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; import matter from "gray-matter"; interface Project { avatar?: string; category: string; description: string; name: string; premium: boolean; url?: string; wip: boolean; } const getProjects = async(): Promise> => { const request = await fetch("https://data.nhcarrigan.com/projects.json"); const data = await request.json(); return data as Array; }; const projectNameMap: Record = { "Naomi's Blog": "blog", "NHCarrigan Documentation": "docs", "Naomi's VSCode Themes": "vscode-themes", "Artists4Palestine Bot": "a4p-bot", "ESLint Config": "eslint-config", "Naomi's Adventure I: An Isekai Story": "naomis-adventure-i", }; const excludedProjects = new Set([ "Translation Service", "Gitea" ]); const convertTitleCaseToKebabCase = (string_: string): string => { return string_.toLowerCase().replace(/\s+/g, "-").replaceAll(/[:']/g, ""); }; describe("projects documentation", () => { it("should include all public projects", {timeout: 10000}, async() => { expect.hasAssertions(); const projects = await getProjects(); const docs = await readdir(join(__dirname, "..", "src", "content", "docs", "projects")); for (const project of projects) { if (excludedProjects.has(project.name)) { continue; } const page = docs.find((d) => { return project.name in projectNameMap ? d.split(".")[0] === projectNameMap[project.name] : d.split(".")[0] === convertTitleCaseToKebabCase(project.name); }); expect(page, `Page not found for project ${project.name}`).toBeDefined(); const content = await readFile(join(__dirname, "..", "src", "content", "docs", "projects", page!), "utf-8"); const { data } = matter(content); expect(data.title, `Title not found for project ${project.name}`).toBeDefined(); expect(data.title, `Title for project ${project.name} is not correct`).toBe(project.name); if (project.wip) { continue; } expect(content).not.toMatch(/:::note\nThis section is coming soon!\n:::/); } }); });