Files
mnote/scripts/task792-openhub-native-mnote-context-source-smoke.js
T

170 lines
6.2 KiB
JavaScript

#!/usr/bin/env node
"use strict";
const fs = require("node:fs");
const path = require("node:path");
const repoRoot = path.resolve(__dirname, "..");
const openHubRoot =
process.env.OPENHUB_RESEARCH_ROOT || "/mnt/Data1T/tmp/mnote-openhub-research/OpenHub";
const files = {
chatInput: path.join(openHubRoot, "smart-query-frontend/src/components/ChatInput.jsx"),
smartQueryPage: path.join(openHubRoot, "smart-query-frontend/src/pages/SmartQueryPage.jsx"),
api: path.join(openHubRoot, "smart-query-frontend/src/services/api.js"),
openHubFrontendSrc: path.join(openHubRoot, "smart-query-frontend/src"),
mnoteOpenHubRoute: path.join(repoRoot, "rust/crates/mnote-web/src/routes/page_ai_openhub.rs"),
};
function read(file) {
return fs.readFileSync(file, "utf8");
}
function walkFiles(dir, result = []) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
walkFiles(fullPath, result);
} else {
result.push(fullPath);
}
}
return result;
}
function extractBetween(source, startNeedle, endNeedle) {
const start = source.indexOf(startNeedle);
if (start < 0) return "";
const end = source.indexOf(endNeedle, start);
return source.slice(start, end < 0 ? undefined : end);
}
function check(failures, name, passed, detail = "") {
if (!passed) {
failures.push(detail ? `${name}: ${detail}` : name);
}
}
const chatInput = read(files.chatInput);
const smartQueryPage = read(files.smartQueryPage);
const api = read(files.api);
const controlRow = extractBetween(chatInput, "<Segmented", "<TextArea");
const handleMNoteSend = extractBetween(chatInput, "const handleSendWithMNoteContext", "useEffect(() =>");
const handleSend = extractBetween(smartQueryPage, "const handleSend = async", "const handleKeyPress");
const legacyFloatingSelectors = [
"mnote-openhub-context-bar",
"mnote-openhub-native-context",
"data-mnote-openhub-ai-quick-actions",
"mnote_openhub_bridge_markup",
"insertAddress(",
];
const frontendLegacyHits = [];
for (const file of walkFiles(files.openHubFrontendSrc)) {
const rel = path.relative(openHubRoot, file);
const source = read(file);
for (const selector of legacyFloatingSelectors) {
if (source.includes(selector)) {
frontendLegacyHits.push(`${rel} -> ${selector}`);
}
}
}
const mnoteRoute = fs.existsSync(files.mnoteOpenHubRoute) ? read(files.mnoteOpenHubRoute) : "";
const mnoteRouteLegacyHits = legacyFloatingSelectors.filter((selector) =>
mnoteRoute.includes(selector)
);
const failures = [];
check(
failures,
"ChatInput owns two native MNote context buttons",
chatInput.includes("isMNoteOpenHubEmbed()") &&
chatInput.includes("requestMNoteActiveTabAddress") &&
chatInput.includes("data-mnote-openhub-current-tab-toggle") &&
chatInput.includes("data-mnote-openhub-current-folder-toggle"),
"missing embed gate, context request helper, or native button data attributes"
);
check(
failures,
"MNote context buttons are in the controls row beside agent/model controls",
controlRow.includes("<Segmented") &&
controlRow.includes("<ModelSelect") &&
controlRow.includes("data-mnote-openhub-current-tab-toggle") &&
controlRow.includes("data-mnote-openhub-current-folder-toggle") &&
controlRow.indexOf("<Segmented") < controlRow.indexOf("data-mnote-openhub-current-tab-toggle") &&
controlRow.indexOf("<ModelSelect") < controlRow.indexOf("data-mnote-openhub-current-folder-toggle"),
"native buttons must be before TextArea and in the same row as agent segmented/model select"
);
check(
failures,
"Icon-only buttons use tooltips and aria labels for current page/tab and folder",
/Tooltip\s+title=["{][^"'}]*(当前(?:页面|激活\s*Tab|Tab)|current\s*(?:page|tab))/i.test(controlRow) &&
/Tooltip\s+title=["{][^"'}]*(当前(?:文件夹|激活\s*Tab\s*的文件夹)|current\s*folder|folder)/i.test(controlRow) &&
/aria-label="当前页面"/.test(controlRow) &&
/aria-label="文件夹"/.test(controlRow) &&
/icon=\{<FileTextOutlined\s*\/>\}/.test(controlRow) &&
/icon=\{<FolderOpenOutlined\s*\/>\}/.test(controlRow) &&
!/>当前页面<\/Button>/.test(controlRow) &&
!/>文件夹<\/Button>/.test(controlRow),
"expected two icon-only buttons with distinct tooltip and aria labels"
);
check(
failures,
"ChatInput sends mnote_context as hidden metadata instead of writing URL into textarea question",
/handleSend\(undefined,\s*context\s*\?\s*\{/.test(handleMNoteSend) &&
handleMNoteSend.includes("kind: mnoteContextMode") &&
handleMNoteSend.includes("value: context.value") &&
!/setQuestion\s*\(\s*context\./.test(handleMNoteSend) &&
!/setQuestion\s*\(\s*mnoteContext/.test(handleMNoteSend) &&
!/question\s*=\s*context\.value/.test(handleMNoteSend),
"context must flow through handleSend second argument and must not mutate the visible question value"
);
check(
failures,
"SmartQueryPage accepts mnote_context and forwards it to queryDataService.queryDataStream",
/const\s+handleSend\s*=\s*async\s*\(\s*overrideQuestion\s*,\s*mnoteContext\s*=\s*null\s*\)/.test(
smartQueryPage
) &&
handleSend.includes("queryDataService.queryDataStream(") &&
/abortControllerRef\.current\.signal\s*,\s*mnoteContext/.test(handleSend),
"handleSend must accept mnoteContext and pass it through the stream send call"
);
check(
failures,
"OpenHub send body carries hidden mnote_context",
/queryDataStream:\s*async\s*\([^)]*mnoteContext\s*=\s*null/.test(api) &&
api.includes("requestBody.mnote_context = mnoteContext") &&
!/question\s*:\s*.*mnoteContext/.test(api),
"api.js must put mnoteContext into requestBody.mnote_context, not append it to question"
);
check(
failures,
"OpenHub frontend no longer depends on legacy MNote floating button selectors",
frontendLegacyHits.length === 0,
frontendLegacyHits.join("; ")
);
check(
failures,
"MNote OpenHub route no longer injects legacy floating context buttons",
mnoteRouteLegacyHits.length === 0,
mnoteRouteLegacyHits.join("; ")
);
if (failures.length) {
console.error("OpenHub native MNote context source smoke failed:");
for (const failure of failures) console.error(`- ${failure}`);
process.exit(1);
}
console.log("OpenHub native MNote context source smoke passed.");