Persist PageTree expand state via control-plane view-state and align chevron/DOM with restored expansion; keep Sidex-style shallow page-tree scan and drop the unused recursive scanner that only added cargo noise. Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi into a module package, and retire Hermes/ACP/OpenHub recycle + root harness evidence from the index while gitignoring recycle and local diag dumps. Archive superseded design/bugs docs under old/, point architecture at ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor regressions so the working tree can stay clean.
94 lines
3.6 KiB
JavaScript
94 lines
3.6 KiB
JavaScript
#!/usr/bin/env node
|
|
"use strict";
|
|
|
|
const fs = require("node:fs");
|
|
const os = require("node:os");
|
|
const path = require("node:path");
|
|
const { chromium } = require("playwright");
|
|
const { execFileSync } = require("node:child_process");
|
|
|
|
const { BASE_URL, UI_TIMEOUT_MS, ensureAuthenticated, assert } = require("./tree-shell-smoke-helpers");
|
|
|
|
const ROOT = fs.mkdtempSync(path.join(os.tmpdir(), "mnote-vault-single-folder-"));
|
|
const ROOT_URI = `file://${ROOT}`;
|
|
|
|
function authCookieHeader() {
|
|
const output = execFileSync(process.execPath, [path.join(__dirname, "mnote-vault-cli.js"), "auth-e2e"], {
|
|
cwd: path.resolve(__dirname, ".."),
|
|
encoding: "utf8",
|
|
stdio: ["ignore", "pipe", "ignore"],
|
|
});
|
|
const match = output.match(/^export MNOTE_COOKIE=(.+)$/m);
|
|
if (!match) throw new Error("auth-e2e 未返回 MNOTE_COOKIE");
|
|
return JSON.parse(match[1]);
|
|
}
|
|
|
|
function writeWorkspaceManifest() {
|
|
const metadataDir = path.join(ROOT, ".mnote");
|
|
fs.mkdirSync(metadataDir, { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(metadataDir, "workspace.json"),
|
|
`${JSON.stringify({
|
|
workspaceId: "local-ws:mnote-e2e:vault-single-folder",
|
|
ownerId: "mnote-e2e",
|
|
createdAt: new Date().toISOString(),
|
|
capabilities: ["local_files", "markdown_edit", "vault"],
|
|
}, null, 2)}\n`,
|
|
"utf8",
|
|
);
|
|
}
|
|
|
|
async function requestJson(context, method, urlPath, data) {
|
|
const response = await context.fetch(`${BASE_URL}${urlPath}`, {
|
|
method,
|
|
headers: { "content-type": "application/json" },
|
|
data,
|
|
timeout: UI_TIMEOUT_MS,
|
|
});
|
|
const payload = await response.json();
|
|
assert(response.ok(), `${urlPath} 请求失败: ${response.status()} ${JSON.stringify(payload)}`);
|
|
return payload.result || payload;
|
|
}
|
|
|
|
async function main() {
|
|
writeWorkspaceManifest();
|
|
const cookieHeader = authCookieHeader();
|
|
const browser = await chromium.launch({ headless: true });
|
|
const context = await browser.newContext({
|
|
viewport: { width: 1180, height: 900 },
|
|
locale: "zh-CN",
|
|
extraHTTPHeaders: { cookie: cookieHeader },
|
|
});
|
|
const page = await context.newPage();
|
|
try {
|
|
await ensureAuthenticated(page, context.request);
|
|
await requestJson(context.request, "POST", "/api/vault/items", {
|
|
rootUri: ROOT_URI,
|
|
title: "单账号服务",
|
|
username: "solo-user",
|
|
password: process.env.MNOTE_VAULT_SMOKE_PASSWORD || "pw[A]",
|
|
folderPath: "个人/密码/单账号服务",
|
|
tags: ["temporary-test-tag"],
|
|
notesMarkdown: "## 临时说明\n\n仅用于 smoke。",
|
|
});
|
|
await page.goto(`${BASE_URL}/vault?sourceKind=local_folder&rootUri=${encodeURIComponent(ROOT_URI)}`, {
|
|
waitUntil: "commit",
|
|
timeout: UI_TIMEOUT_MS,
|
|
});
|
|
await page.locator('[data-testid="mnote-vault-workbench"]').waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
|
await page.locator('[data-testid="vault-list-item"]', { hasText: "单账号服务" }).waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
|
|
const folderText = await page.locator('[data-testid="vault-list"]').innerText({ timeout: UI_TIMEOUT_MS });
|
|
assert(!folderText.includes("单账号服务\n单账号服务"), "单账号文件夹不应再渲染成同名可折叠父节点 + 子条目");
|
|
const singleFolderHeaders = await page.locator('[data-vault-toggle-folder]', { hasText: "单账号服务" }).count();
|
|
assert(singleFolderHeaders === 0, `单账号文件夹不应出现可折叠标题,实际 ${singleFolderHeaders}`);
|
|
console.log(JSON.stringify({ ok: true, rootUri: ROOT_URI }));
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error.stack || error.message || String(error));
|
|
process.exit(1);
|
|
});
|