收口工作台资源 tab 与本地文件树 P1
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
#!/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);
|
||||
});
|
||||
Reference in New Issue
Block a user