2026-06-01 21:10:37 +08:00
import fs from "node:fs" ;
import path from "node:path" ;
function assert ( condition : unknown , message : string ) {
if ( ! condition ) throw new Error ( message );
}
export function runTests() {
const projectRoot = process . cwd ();
const dashboardSource = fs . readFileSync (
path . join ( projectRoot , "components" , "dashboard" , "ScanTerminalDashboard.tsx" ),
"utf8" ,
);
const chartSource = fs . readFileSync (
path . join ( projectRoot , "components" , "dashboard" , "scan-terminal" , "LiveTemperatureThresholdChart.tsx" ),
"utf8" ,
);
const modalPath = path . join ( projectRoot , "components" , "dashboard" , "scan-terminal" , "UserFeedbackModal.tsx" );
2026-06-06 22:55:15 +08:00
const statusButtonPath = path . join ( projectRoot , "components" , "dashboard" , "scan-terminal" , "UserFeedbackStatusButton.tsx" );
2026-06-01 21:10:37 +08:00
const opsSidebarSource = fs . readFileSync (
path . join ( projectRoot , "components" , "ops" , "layout" , "AdminSidebar.tsx" ),
"utf8" ,
);
const opsApiSource = fs . readFileSync ( path . join ( projectRoot , "lib" , "ops-api.ts" ), "utf8" );
2026-06-06 22:55:15 +08:00
const feedbackRouteSource = fs . readFileSync ( path . join ( projectRoot , "app" , "api" , "feedback" , "route.ts" ), "utf8" );
2026-06-01 21:10:37 +08:00
assert ( fs . existsSync ( modalPath ), "terminal must ship a user feedback modal component" );
2026-06-06 22:55:15 +08:00
assert ( fs . existsSync ( statusButtonPath ), "terminal must ship a user-facing feedback status notification component" );
2026-06-01 21:10:37 +08:00
const modalSource = fs . readFileSync ( modalPath , "utf8" );
2026-06-06 22:55:15 +08:00
const statusButtonSource = fs . readFileSync ( statusButtonPath , "utf8" );
const statusHelperSource = fs . readFileSync (
path . join ( projectRoot , "components" , "dashboard" , "scan-terminal" , "feedback-status.ts" ),
"utf8" ,
);
2026-06-01 21:10:37 +08:00
assert (
dashboardSource . includes ( "onFeedbackClick" ) &&
dashboardSource . includes ( "<UserFeedbackModal" ) &&
dashboardSource . includes ( "setFeedbackDraft" ),
"terminal sidebar must expose a feedback entry that opens the shared modal" ,
);
assert (
chartSource . includes ( "onReportIssue" ) &&
chartSource . includes ( "Bug" ) &&
chartSource . includes ( "detailError" ),
"each chart must expose a report-this-chart action with chart loading/error context" ,
);
assert (
modalSource . includes ( "/api/feedback" ) &&
modalSource . includes ( "getAnalyticsClientId" ) &&
2026-06-06 23:30:08 +08:00
modalSource . includes ( "getSupabaseBrowserClient" ) &&
modalSource . includes ( "hasSupabasePublicEnv" ) &&
modalSource . includes ( ".auth.getSession()" ) &&
modalSource . includes ( "readOnly={Boolean(loginEmailContact)}" ) &&
2026-06-06 22:55:15 +08:00
modalSource . includes ( "onSubmitted" ) &&
2026-06-01 21:10:37 +08:00
modalSource . includes ( "navigator.userAgent" ) &&
modalSource . includes ( "type=\"textarea\"" ) === false ,
2026-06-06 23:30:08 +08:00
"feedback modal must submit to the feedback API, lock contact to the login email when available, notify the dashboard after success, and attach client/session diagnostics without using invalid textarea input types" ,
2026-06-06 22:55:15 +08:00
);
2026-06-10 12:31:14 +08:00
assert (
modalSource . includes ( "formatFeedbackSubmitError" ) &&
modalSource . includes ( "looksLikeHtmlDocument" ) &&
! modalSource . includes ( "throw new Error(detail || `HTTP ${res.status}`)" ),
"feedback modal must not expose raw Cloudflare/HTML error pages to users" ,
);
2026-06-06 22:55:15 +08:00
assert (
feedbackRouteSource . includes ( "export async function GET" ) &&
feedbackRouteSource . includes ( "method: \"GET\"" ) &&
feedbackRouteSource . includes ( "limit" ),
"feedback API proxy must expose a GET endpoint for the current user's feedback status list" ,
);
assert (
dashboardSource . includes ( "<UserFeedbackStatusButton" ) &&
dashboardSource . includes ( "feedbackRefreshKey" ) &&
dashboardSource . includes ( "setFeedbackRefreshKey" ),
"terminal header must expose a small feedback status notification icon and refresh it after submissions" ,
);
assert (
statusButtonSource . includes ( "/api/feedback" ) &&
statusButtonSource . includes ( "localStorage" ) &&
statusButtonSource . includes ( "Bell" ) &&
statusHelperSource . includes ( "triaged" ) &&
statusHelperSource . includes ( "resolved" ),
"feedback status notification must poll the user feedback API, badge unseen status changes, and translate handled states" ,
2026-06-01 21:10:37 +08:00
);
assert (
opsSidebarSource . includes ( "/ops/feedback" ) &&
opsApiSource . includes ( "feedback(" ) &&
opsApiSource . includes ( "updateFeedbackStatus" ),
"ops must expose a feedback inbox in navigation and API client" ,
);
}