Files
NetMesh/components/GlobalSftpTransferCenter.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

242 lines
8.2 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import type { TransferTask } from "../domain/models";
import {
buildGlobalTransferProgressDisplay,
getGlobalConflictActionPresentation,
getGlobalTransferBadge,
getGlobalTransferBatchEligibility,
getGlobalTransferStatusOverride,
getGlobalTransferBucket,
getTasksForGlobalTransferBucket,
isDirectoryParentTask,
listChildTasksForParent,
pickActiveChildSummaries,
shouldShowCollapsedActiveChildren,
splitBackgroundTransfers,
} from "./GlobalSftpTransferCenter";
test("folder conflicts emphasize merge and mark replace as destructive in the transfer center", () => {
assert.deepEqual(getGlobalConflictActionPresentation("merge", true), {
variant: "default",
destructiveReplace: false,
});
assert.deepEqual(getGlobalConflictActionPresentation("replace", true), {
variant: "outline",
destructiveReplace: true,
});
assert.deepEqual(getGlobalConflictActionPresentation("replace", false), {
variant: "default",
destructiveReplace: false,
});
});
const task = (id: string, status: TransferTask["status"], background = false) => ({
id,
fileName: id,
sourcePath: "/a",
targetPath: "/b",
sourceConnectionId: "local",
targetConnectionId: "remote",
direction: "upload" as const,
status,
totalBytes: 10,
transferredBytes: 1,
speed: 1,
startTime: 1,
isDirectory: false,
background,
});
test("global transfer statuses keep paused work separate from items needing attention", () => {
assert.equal(getGlobalTransferBucket(task("a", "transferring")), "active");
assert.equal(getGlobalTransferBucket(task("a", "pausing")), "active");
assert.equal(getGlobalTransferBucket(task("a", "queued")), "queued");
assert.equal(getGlobalTransferBucket(task("a", "paused")), "paused");
assert.equal(getGlobalTransferBucket(task("a", "interrupted")), "attention");
assert.equal(getGlobalTransferBucket(task("a", "attention")), "attention");
assert.equal(getGlobalTransferBucket(task("a", "failed")), "attention");
assert.equal(getGlobalTransferBucket(task("a", "completed")), "completed");
assert.equal(getGlobalTransferBucket(task("a", "cancelled")), "completed");
});
test("batch actions are disabled when no task can accept them", () => {
assert.deepEqual(getGlobalTransferBatchEligibility([
task("done", "completed"),
task("cancelled", "cancelled"),
]), { pausableCount: 0, resumableCount: 0 });
assert.deepEqual(getGlobalTransferBatchEligibility([
task("running", "transferring"),
task("waiting", "queued"),
task("paused", "paused"),
{ ...task("failed", "failed"), checkpointBytes: 4 },
]), { pausableCount: 2, resumableCount: 2 });
});
test("paused transfer errors remain visible instead of looking safely paused", () => {
assert.equal(getGlobalTransferStatusOverride({
error: "Upload resume is unavailable",
pauseUnavailableReason: undefined,
}), "Upload resume is unavailable");
});
test("the all bucket includes every top-level task regardless of status", () => {
const tasks = [
task("a", "transferring"),
task("b", "queued"),
task("c", "failed"),
{ ...task("child", "transferring"), parentTaskId: "a" },
];
assert.deepEqual(getTasksForGlobalTransferBucket(tasks, "all").map((item) => item.id), ["a", "b", "c"]);
});
test("badge counts active and queued work while surfacing attention", () => {
assert.deepEqual(getGlobalTransferBadge([
task("a", "transferring"),
task("b", "queued"),
task("c", "failed"),
]), { count: 2, hasAttention: true });
});
test("badge does not double count child files in a folder transfer", () => {
assert.deepEqual(getGlobalTransferBadge([
task("parent", "transferring"),
{ ...task("child", "transferring"), parentTaskId: "parent" },
]), { count: 1, hasAttention: false });
});
test("successful background work is collapsed but failures stay visible", () => {
const split = splitBackgroundTransfers([
task("a", "completed", true),
task("b", "failed", true),
task("c", "completed", false),
]);
assert.deepEqual(split.visible.map((item) => item.id), ["b", "c"]);
assert.deepEqual(split.collapsed.map((item) => item.id), ["a"]);
});
test("directory parent detection matches side-queue file-count semantics", () => {
assert.equal(isDirectoryParentTask({
isDirectory: true,
parentTaskId: undefined,
progressMode: "files",
}), true);
assert.equal(isDirectoryParentTask({
isDirectory: true,
parentTaskId: undefined,
progressMode: undefined,
}), true);
assert.equal(isDirectoryParentTask({
isDirectory: true,
parentTaskId: "p",
progressMode: "files",
}), false);
assert.equal(isDirectoryParentTask({
isDirectory: false,
parentTaskId: undefined,
progressMode: "bytes",
}), false);
assert.equal(isDirectoryParentTask({
isDirectory: true,
parentTaskId: undefined,
progressMode: "bytes",
}), false);
});
test("folder parent progress uses progressive discovery labels not byte formatting", () => {
const t = (key: string, params?: Record<string, string | number>) => {
if (key === "sftp.transfers.filesDiscoveredProgress") {
return `${params?.completed} done · ${params?.discovered} found`;
}
if (key === "sftp.transfers.filesProgress") {
return `${params?.current}/${params?.total} files`;
}
if (key === "sftp.transfers.filesCount") {
return `${params?.count} files`;
}
return key;
};
const display = buildGlobalTransferProgressDisplay({
status: "transferring",
isDirectory: true,
progressMode: "files",
totalBytes: 12,
transferredBytes: 1,
speed: 0,
phase: "transferring",
}, t);
assert.equal(display.percent, (1 / 12) * 100);
assert.equal(display.detail, "1 done · 12 found");
assert.equal(display.indeterminate, false);
// Must not look like "1 Bytes / 12 Bytes"
assert.doesNotMatch(display.detail, /Bytes/i);
});
test("folder parent scanning stays indeterminate while nothing has completed", () => {
const t = (key: string, params?: Record<string, string | number>) => {
if (key === "sftp.transfers.filesDiscoveredProgress") {
return `${params?.completed} done · ${params?.discovered} found`;
}
return key;
};
const display = buildGlobalTransferProgressDisplay({
status: "transferring",
isDirectory: true,
progressMode: "files",
totalBytes: 40,
transferredBytes: 0,
speed: 0,
phase: "scanning",
}, t);
assert.equal(display.detail, "0 done · 40 found");
assert.equal(display.indeterminate, true);
});
test("single-file progress still shows byte totals", () => {
const t = (key: string) => key;
const display = buildGlobalTransferProgressDisplay({
status: "transferring",
isDirectory: false,
progressMode: "bytes",
totalBytes: 1024,
transferredBytes: 512,
speed: 0,
}, t);
assert.equal(display.percent, 50);
assert.match(display.detail, /512\s*Bytes/);
assert.match(display.detail, /1(\.00)?\s*KB/);
});
test("active child summary prefers transferring files over queued", () => {
const children = [
{ ...task("q1", "queued"), parentTaskId: "p", startTime: 1 },
{ ...task("t1", "transferring"), parentTaskId: "p", startTime: 2 },
{ ...task("t2", "transferring"), parentTaskId: "p", startTime: 3 },
{ ...task("done", "completed"), parentTaskId: "p", startTime: 0 },
];
assert.deepEqual(
pickActiveChildSummaries(children, 2).map((item) => item.id),
["t1", "t2"],
);
});
test("collapsed active children only show while transferring or soft-draining", () => {
assert.equal(shouldShowCollapsedActiveChildren("transferring"), true);
assert.equal(shouldShowCollapsedActiveChildren("pausing"), true);
assert.equal(shouldShowCollapsedActiveChildren("paused"), false);
assert.equal(shouldShowCollapsedActiveChildren("interrupted"), false);
assert.equal(shouldShowCollapsedActiveChildren("queued"), false);
});
test("listChildTasksForParent skips cancelled siblings", () => {
const tasks = [
task("p", "transferring"),
{ ...task("c1", "transferring"), parentTaskId: "p" },
{ ...task("c2", "cancelled"), parentTaskId: "p" },
{ ...task("other", "transferring"), parentTaskId: "x" },
];
assert.deepEqual(listChildTasksForParent(tasks, "p").map((item) => item.id), ["c1"]);
});