feat: build local skills registry
This commit is contained in:
162
tests/audit-skills-ui.test.ts
Normal file
162
tests/audit-skills-ui.test.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
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 convex list 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\.list,\s*\{\s*limit:\s*100\s*\}\s*\)/,
|
||||
"AuditsBoard should call api.audits.list with { limit: 100 }.",
|
||||
);
|
||||
assert.match(
|
||||
boardSource,
|
||||
/sort\(\(\s*a,\s*b\s*\)\s*=>\s*b\.createdAt\s*-\s*a\.createdAt\)/,
|
||||
"Audits 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,
|
||||
/href=\{`\/dashboard\/audits\/\$\{audit\._id\}`\}/,
|
||||
"Each audit row should link to /dashboard/audits/{id}.",
|
||||
);
|
||||
});
|
||||
|
||||
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.",
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user