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
70 lines
4.0 KiB
TypeScript
70 lines
4.0 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import React from "react";
|
|
import { createDomRenderer, dispatchDomEvent, flushEffects, installDomEnvironment } from "./test-support/renderReactDom.tsx";
|
|
|
|
test("large transfer histories mount only visible rows and can scroll to the last file", async (t) => {
|
|
const env = installDomEnvironment();
|
|
const previous = new Map<string, PropertyDescriptor | undefined>();
|
|
const globals = {
|
|
MutationObserver: env.window.MutationObserver,
|
|
NodeFilter: env.window.NodeFilter,
|
|
HTMLInputElement: env.window.HTMLInputElement,
|
|
Element: env.window.Element,
|
|
requestAnimationFrame: (callback: FrameRequestCallback) => setTimeout(() => callback(Date.now()), 0),
|
|
cancelAnimationFrame: clearTimeout,
|
|
};
|
|
for (const [key, value] of Object.entries(globals)) {
|
|
previous.set(key, Object.getOwnPropertyDescriptor(globalThis, key));
|
|
Object.defineProperty(globalThis, key, { configurable: true, writable: true, value });
|
|
}
|
|
Object.defineProperties(env.window.HTMLElement.prototype, {
|
|
offsetWidth: { configurable: true, get: () => 460 },
|
|
offsetHeight: { configurable: true, get() { return this.dataset.section === "global-sftp-transfer-list" ? 460 : 112; } },
|
|
});
|
|
const { I18nProvider } = await import("../application/i18n/I18nProvider.tsx");
|
|
const { sftpTransferCenterStore: store } = await import("../application/state/sftpTransferCenterStore.ts");
|
|
const { GlobalSftpTransferCenter } = await import("./GlobalSftpTransferCenter.tsx");
|
|
const { TooltipProvider } = await import("./ui/tooltip.tsx");
|
|
const renderer = await createDomRenderer(env.document);
|
|
t.after(async () => {
|
|
await renderer.unmount();
|
|
for (const [key, descriptor] of previous) {
|
|
if (descriptor) Object.defineProperty(globalThis, key, descriptor);
|
|
else Reflect.deleteProperty(globalThis, key);
|
|
}
|
|
env.cleanup();
|
|
});
|
|
store.publishOwner("virtual-list-test", Array.from({ length: 1_000 }, (_, index) => ({
|
|
id: `virtual-file-${index}`, fileName: `file-${index}.bin`,
|
|
sourcePath: `/source/${index}`, targetPath: `/target/${index}`,
|
|
sourceConnectionId: "source", targetConnectionId: "local", direction: "download" as const,
|
|
status: "queued" as const, totalBytes: 1_000, transferredBytes: 0, speed: 0,
|
|
startTime: index + 1, isDirectory: false,
|
|
})));
|
|
await renderer.render(<I18nProvider locale="en"><TooltipProvider><GlobalSftpTransferCenter /></TooltipProvider></I18nProvider>);
|
|
const toggle = env.document.querySelector("[data-section=global-sftp-transfer-toggle]");
|
|
assert.ok(toggle);
|
|
await dispatchDomEvent(toggle, new env.window.MouseEvent("click", { bubbles: true }));
|
|
await flushEffects();
|
|
const rows = () => env.document.querySelectorAll("[role=progressbar]");
|
|
assert.ok(rows().length > 0 && rows().length < 40, `rendered ${rows().length} rows`);
|
|
assert.ok(env.document.querySelector('[role=progressbar][aria-label="file-999.bin"]'));
|
|
const firstRow = env.document.querySelector('[data-transfer-status="queued"]');
|
|
assert.ok(firstRow);
|
|
assert.equal(firstRow.matches('.last\\:border-b-0:last-child'), false, "a virtual row must not lose its divider just because it has a wrapper");
|
|
|
|
const scroll = env.document.querySelector<HTMLElement>("[data-section=global-sftp-transfer-list]");
|
|
assert.ok(scroll);
|
|
scroll.scrollTop = 112 * 1_000 - 460;
|
|
await dispatchDomEvent(scroll, new env.window.Event("scroll"));
|
|
await flushEffects();
|
|
assert.ok(rows().length > 0 && rows().length < 40);
|
|
assert.ok(env.document.querySelector('[role=progressbar][aria-label="file-0.bin"]'));
|
|
const lastRow = env.document.querySelector('[role=progressbar][aria-label="file-0.bin"]')?.closest('[data-transfer-status]');
|
|
assert.ok(lastRow?.classList.contains("border-b-0"), "only the actual final row omits its divider");
|
|
assert.equal(store.getSnapshot().tasks.length, 1_000, "offscreen files must remain queued");
|
|
await dispatchDomEvent(toggle, new env.window.MouseEvent("click", { bubbles: true }));
|
|
await flushEffects();
|
|
});
|