Files
mnote/scripts/task472-local-folder-sort-order-persistence-smoke.js
T

147 lines
4.9 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
"use strict";
const assert = require("node:assert/strict");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const BASE_URL = (process.env.MNOTE_WEB_SMOKE_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, "");
const ACTOR_ID = "user_real";
function fileUrl(localPath) {
return `file://${localPath}`;
}
function writeWorkspaceManifest(root) {
fs.mkdirSync(path.join(root, ".mnote"), { recursive: true });
fs.writeFileSync(
path.join(root, ".mnote", "workspace.json"),
`${JSON.stringify({
workspaceId: "local-ws:user_real:task472",
ownerId: ACTOR_ID,
createdAt: new Date().toISOString(),
capabilities: ["local_files", "tree_commands", "markdown_edit"],
}, null, 2)}\n`,
"utf8",
);
}
async function requestJson(requestPath, init = {}) {
const response = await fetch(`${BASE_URL}${requestPath}`, {
...init,
headers: {
...(init.body ? { "content-type": "application/json" } : {}),
"x-mnote-actor-id": ACTOR_ID,
"x-mnote-actor-type": "user",
...(init.headers || {}),
},
});
const text = await response.text();
let payload = null;
try {
payload = text ? JSON.parse(text) : null;
} catch {
payload = text;
}
if (!response.ok) {
throw new Error(`${requestPath} 请求失败: ${response.status} ${typeof payload === "string" ? payload : JSON.stringify(payload)}`);
}
return payload;
}
async function readFileTreeProjection(rootUri) {
const url = new URL(`${BASE_URL}/api/tree/projections/file`);
url.searchParams.set("sourceKind", "local_folder");
url.searchParams.set("rootUri", rootUri);
const payload = await requestJson(`${url.pathname}${url.search}`);
const result = payload.result || payload;
assert.equal(result.sourceKind, "local_folder", `projection 应来自 local_folder: ${JSON.stringify(result)}`);
assert.equal(result.projection, "file_tree", `projection 类型应为 file_tree: ${JSON.stringify(result)}`);
assert(Array.isArray(result.items), `projection items 应为数组: ${JSON.stringify(result)}`);
return result;
}
function rootMarkdownOrder(projection) {
return projection.items
.filter((item) => item && item.parentNodeId == null && item.rowKind === "markdown")
.map((item) => item.resourceMeta?.extra?.source?.relativePath || "");
}
function readPersistedOrder(root) {
const orderPath = path.join(root, ".mnote", "file-order.json");
assert(fs.existsSync(orderPath), "move sortOrder 后应写入 .mnote/file-order.json");
return JSON.parse(fs.readFileSync(orderPath, "utf8"));
}
async function moveWithSortOrder(rootUri, documentId, sortOrder) {
const payload = await requestJson("/api/tree/commands", {
method: "POST",
body: JSON.stringify({
action: "move",
sourceKind: "local_folder",
rootUri,
documentId,
parentId: null,
sortOrder,
}),
});
assert.equal(payload.result?.execution?._unsupportedFields, undefined, `sortOrder 不应再落入 _unsupportedFields: ${JSON.stringify(payload)}`);
return payload;
}
async function main() {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "mnote-task472-sort-order-"));
const rootUri = fileUrl(root);
try {
writeWorkspaceManifest(root);
fs.writeFileSync(path.join(root, "alpha.md"), "# Alpha\n", "utf8");
fs.writeFileSync(path.join(root, "beta.md"), "# Beta\n", "utf8");
fs.writeFileSync(path.join(root, "source.md"), "# Source\n", "utf8");
const initialProjection = await readFileTreeProjection(rootUri);
assert.deepEqual(
rootMarkdownOrder(initialProjection).slice(0, 3),
["alpha.md", "beta.md", "source.md"],
"初始 projection 应按文件系统自然顺序作为基线",
);
await moveWithSortOrder(rootUri, "local-md:source.md", 0);
const immediateProjection = await readFileTreeProjection(rootUri);
assert.deepEqual(
rootMarkdownOrder(immediateProjection).slice(0, 3),
["source.md", "alpha.md", "beta.md"],
"move sortOrder 后立即重新加载 projection 应保持新顺序",
);
const persistedOrder = readPersistedOrder(root);
assert(JSON.stringify(persistedOrder).includes("source.md"), `file-order 应记录 source.md: ${JSON.stringify(persistedOrder)}`);
const reloadedProjection = await readFileTreeProjection(rootUri);
assert.deepEqual(
rootMarkdownOrder(reloadedProjection).slice(0, 3),
["source.md", "alpha.md", "beta.md"],
"再次重新加载 projection 顺序不应回退到自然排序",
);
console.log(JSON.stringify({
ok: true,
root,
rootUri,
order: rootMarkdownOrder(reloadedProjection).slice(0, 3),
persistedOrder,
}, null, 2));
} finally {
if (!process.env.MNOTE_KEEP_SMOKE_TMP) {
fs.rmSync(root, { recursive: true, force: true });
}
}
}
main().catch((error) => {
console.error(error);
process.exit(1);
});