Files
NetMesh/lib/sftpFileUtils.test.ts
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

146 lines
4.1 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import {
captureDropPayload,
formatDropScanLabel,
getFileExtension,
hasFileExtension,
localTreeToDropEntries,
materializeDropEntries,
type LocalTreeListEntry,
} from "./sftpFileUtils.ts";
test("hasFileExtension identifies extensionless and dotted filenames", () => {
assert.equal(hasFileExtension("nginx"), false);
assert.equal(hasFileExtension("my-binary"), false);
assert.equal(hasFileExtension(".git"), false);
assert.equal(getFileExtension("nginx"), "file");
assert.equal(hasFileExtension("readme.txt"), true);
assert.equal(hasFileExtension(".bashrc"), false);
assert.equal(hasFileExtension("archive.tar.gz"), true);
});
test("formatDropScanLabel summarizes dropped root names", () => {
assert.equal(formatDropScanLabel([]), "Scanning files...");
assert.equal(formatDropScanLabel([{ name: "docs", isDirectory: true }]), "docs");
assert.equal(
formatDropScanLabel([
{ name: "a", isDirectory: true },
{ name: "b", isDirectory: true },
]),
"a, b",
);
assert.equal(
formatDropScanLabel([
{ name: "a", isDirectory: true },
{ name: "b", isDirectory: true },
{ name: "c", isDirectory: true },
]),
"a, b +1",
);
});
test("captureDropPayload reads webkit entries synchronously", () => {
const file = new File(["x"], "note.txt");
Object.defineProperty(file, "path", { value: "/tmp/note.txt" });
const dataTransfer = {
items: [{
kind: "file",
getAsFile: () => file,
webkitGetAsEntry: () => ({
name: "note.txt",
isFile: true,
isDirectory: false,
}),
}],
files: [file],
} as unknown as DataTransfer;
const payload = captureDropPayload(dataTransfer);
assert.equal(payload.roots.length, 1);
assert.equal(payload.roots[0].name, "note.txt");
assert.equal(payload.roots[0].isDirectory, false);
assert.equal(payload.roots[0].localPath, "/tmp/note.txt");
});
test("materializeDropEntries prefers listLocalTree for directory roots with paths", async () => {
const progress: Array<{ fileCount: number; directoryCount: number }> = [];
const tree: LocalTreeListEntry[] = [
{
localPath: "/tmp/project",
relativePath: "project",
type: "directory",
size: 0,
lastModified: 1,
},
{
localPath: "/tmp/project/src",
relativePath: "project/src",
type: "directory",
size: 0,
lastModified: 1,
},
{
localPath: "/tmp/project/src/main.ts",
relativePath: "project/src/main.ts",
type: "file",
size: 12,
lastModified: 2,
},
];
const entries = await materializeDropEntries(
{
roots: [{
name: "project",
isDirectory: true,
localPath: "/tmp/project",
}],
filesFallback: [],
},
{
listLocalTree: async (path, options) => {
assert.equal(path, "/tmp/project");
options?.onProgress?.({ fileCount: 1, directoryCount: 2, entryCount: 3 });
return tree;
},
onProgress: (p) => progress.push({ fileCount: p.fileCount, directoryCount: p.directoryCount }),
},
);
assert.deepEqual(
entries.map((entry) => ({
relativePath: entry.relativePath,
isDirectory: entry.isDirectory,
localPath: entry.localPath,
size: entry.size,
})),
[
{ relativePath: "project", isDirectory: true, localPath: "/tmp/project", size: undefined },
{ relativePath: "project/src", isDirectory: true, localPath: "/tmp/project/src", size: undefined },
{
relativePath: "project/src/main.ts",
isDirectory: false,
localPath: "/tmp/project/src/main.ts",
size: 12,
},
],
);
assert.ok(progress.some((p) => p.fileCount === 1 && p.directoryCount === 2));
});
test("localTreeToDropEntries preserves file sizes without File handles", () => {
const entries = localTreeToDropEntries([{
localPath: "/tmp/a.txt",
relativePath: "a.txt",
type: "file",
size: 42,
lastModified: 9,
}]);
assert.equal(entries[0].file, null);
assert.equal(entries[0].localPath, "/tmp/a.txt");
assert.equal(entries[0].size, 42);
});