173 lines
5.2 KiB
TypeScript
173 lines
5.2 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import test from "node:test";
|
|
|
|
const source = async (relativePath: string) => {
|
|
return await readFile(
|
|
join(process.cwd(), ...relativePath.split("/")),
|
|
"utf8",
|
|
);
|
|
};
|
|
|
|
test("audits dashboard page uses a dedicated board component", async () => {
|
|
const dashboardPageSource = await source("app/dashboard/audits/page.tsx");
|
|
|
|
assert.doesNotMatch(
|
|
dashboardPageSource,
|
|
/DashboardPlaceholderPage/i,
|
|
"Dashboard audits route should not render the placeholder page.",
|
|
);
|
|
assert.match(
|
|
dashboardPageSource,
|
|
/<AuditsBoard \/>/,
|
|
"Audits board should be mounted from route page.",
|
|
);
|
|
assert.match(
|
|
dashboardPageSource,
|
|
/"@\/components\/audits\/audits-board"/,
|
|
"Audits board should be imported from components.",
|
|
);
|
|
});
|
|
|
|
test("audits board renders compact list with dashboard rows query and core columns", async () => {
|
|
const boardSource = await source("components/audits/audits-board.tsx");
|
|
|
|
assert.match(
|
|
boardSource,
|
|
/\"use client\"/,
|
|
"AuditsBoard must be a Client Component for useQuery.",
|
|
);
|
|
assert.match(
|
|
boardSource,
|
|
/useQuery\s*\(\s*api\.audits\.listDashboardRows,\s*\{\s*limit:\s*100\s*\}\s*\)/,
|
|
"AuditsBoard should call api.audits.listDashboardRows with { limit: 100 }.",
|
|
);
|
|
assert.match(
|
|
boardSource,
|
|
/sort\(\(\s*a,\s*b\s*\)\s*=>\s*b\.updatedAt\s*-\s*a\.updatedAt\)/,
|
|
"Dashboard rows should be sorted newest first.",
|
|
);
|
|
assert.match(boardSource, /Loading|lädt|Lade/i);
|
|
assert.match(boardSource, /Keine Audits|keine Audits/i);
|
|
assert.match(boardSource, /Slug/);
|
|
assert.match(boardSource, /Domain/);
|
|
assert.match(boardSource, /Status/);
|
|
assert.match(boardSource, /Seiten/);
|
|
assert.match(boardSource, /Generierung läuft|Generierung laeuft/);
|
|
assert.match(boardSource, /Fehlgeschlagen/);
|
|
assert.match(boardSource, /Wartet auf finales Audit/);
|
|
assert.match(boardSource, /Wartet auf Start/);
|
|
assert.match(boardSource, /Abgebrochen/);
|
|
assert.match(
|
|
boardSource,
|
|
/href=\{row\.detailHref\}/,
|
|
"Final audit rows should link through their detailHref.",
|
|
);
|
|
assert.match(
|
|
boardSource,
|
|
/row\.kind\s*===\s*"audit"/,
|
|
"Board should branch between final audit rows and generation rows.",
|
|
);
|
|
});
|
|
|
|
test("audit detail component uses getDetail query and renders skills overview section", async () => {
|
|
const detailSource = await source("components/audits/audit-detail.tsx");
|
|
|
|
assert.match(
|
|
detailSource,
|
|
/\"use client\"/,
|
|
"AuditDetail must be client-side for Convex query calls.",
|
|
);
|
|
assert.match(
|
|
detailSource,
|
|
/api\.audits[\s\S]{0,80}getDetail/,
|
|
"AuditDetail should use api.audits.getDetail query.",
|
|
);
|
|
assert.match(
|
|
detailSource,
|
|
/useQuery\(\s*api\.audits\.getDetail,\s*\{/,
|
|
"AuditDetail should call useQuery with api.audits.getDetail directly.",
|
|
);
|
|
assert.doesNotMatch(
|
|
detailSource,
|
|
/const\s+auditDetailQueryRef/,
|
|
"AuditDetail should not use a cast-based query fallback variable.",
|
|
);
|
|
assert.match(
|
|
detailSource,
|
|
/const\s+audit\s*=\s*result\?\.audit;/,
|
|
"AuditDetail should destructure audit from result.audit.",
|
|
);
|
|
assert.match(
|
|
detailSource,
|
|
/const\s+lead\s*=\s*result\?\.lead;/,
|
|
"AuditDetail should destructure lead from result.lead.",
|
|
);
|
|
assert.match(
|
|
detailSource,
|
|
/leadSummary\(\s*lead\s*\)/,
|
|
"AuditDetail should pass lead into leadSummary from result.lead.",
|
|
);
|
|
assert.match(
|
|
detailSource,
|
|
/usedSkills/,
|
|
"AuditDetail should inspect usedSkills for overview rendering.",
|
|
);
|
|
assert.match(
|
|
detailSource,
|
|
/Keine Skills gespeichert/,
|
|
"AuditDetail should show fallback text when no skills are saved.",
|
|
);
|
|
assert.match(
|
|
detailSource,
|
|
/Verwendete Skills/,
|
|
"AuditDetail should render Verwendete Skills heading.",
|
|
);
|
|
assert.match(
|
|
detailSource,
|
|
/Lead|lead/,
|
|
"AuditDetail should surface lead context when available.",
|
|
);
|
|
assert.doesNotMatch(
|
|
detailSource,
|
|
/<p[^>]*>\s*\{leadSummary\(\s*lead\|[\s\S]*?\)\s*\}\s*<\/p>/,
|
|
"Lead summary should not wrap leadSummary output in a nested <p>.",
|
|
);
|
|
assert.doesNotMatch(
|
|
detailSource,
|
|
/<p[^>]*>\s*\{leadSummary\(\s*audit\.lead\)\s*\}\s*<\/p>/,
|
|
"Lead summary should not wrap leadSummary output in a nested <p>.",
|
|
);
|
|
});
|
|
|
|
test("audits detail route passes id to AuditDetail via Promise params", async () => {
|
|
const pageSource = await source("app/dashboard/audits/[id]/page.tsx");
|
|
|
|
assert.match(
|
|
pageSource,
|
|
/params:\s*Promise<\{\s*id:\s*string\s*\}>/,
|
|
"Audit detail route should accept params as Promise in Next.js 16 style.",
|
|
);
|
|
assert.match(
|
|
pageSource,
|
|
/const \{\s*id\s*\}\s*=\s*await params/,
|
|
"Audit detail route should unwrap Promise params.",
|
|
);
|
|
assert.match(
|
|
pageSource,
|
|
/<AuditDetail\s+id=/,
|
|
"Audit detail route should pass id prop into AuditDetail.",
|
|
);
|
|
});
|
|
|
|
test("public audit page does not expose used skills", async () => {
|
|
const publicAuditSource = await source("app/audit/[slug]/page.tsx");
|
|
|
|
assert.doesNotMatch(
|
|
publicAuditSource,
|
|
/Verwendete Skills|usedSkills/i,
|
|
"Public audit page must not show used skills.",
|
|
);
|
|
});
|