147 lines
4.9 KiB
JavaScript
147 lines
4.9 KiB
JavaScript
#!/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);
|
|
});
|