mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-10-29 18:11:40 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07d1a6f2c1 |
No files matched your search
@@ -0,0 +1,71 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
createDomRenderer,
|
||||
dispatchDomEvent,
|
||||
flushEffects,
|
||||
installDomEnvironment,
|
||||
} from "../test-support/renderReactDom.tsx";
|
||||
|
||||
const cases = [
|
||||
{ name: "file", isDirectory: false, existingType: "file", action: "replace", label: "Replace" },
|
||||
{ name: "folder", isDirectory: true, existingType: "directory", action: "merge", label: "Merge" },
|
||||
{ name: "folder with a file target", isDirectory: true, existingType: "file", action: "duplicate", label: "Duplicate" },
|
||||
{ name: "folder with unknown target type", isDirectory: true, existingType: undefined, action: "duplicate", label: "Duplicate" },
|
||||
{ name: "legacy file with unknown target type", isDirectory: false, existingType: undefined, action: "duplicate", label: "Duplicate" },
|
||||
] as const;
|
||||
|
||||
for (const scenario of cases) {
|
||||
test(`initial ${scenario.name} conflict focuses ${scenario.label} and runs that action`, async (t) => {
|
||||
const env = installDomEnvironment();
|
||||
const previousMutationObserver = globalThis.MutationObserver;
|
||||
const previousNodeFilter = globalThis.NodeFilter;
|
||||
const previousHTMLInputElement = globalThis.HTMLInputElement;
|
||||
Object.defineProperty(globalThis, "MutationObserver", { configurable: true, writable: true, value: env.window.MutationObserver });
|
||||
Object.defineProperty(globalThis, "NodeFilter", { configurable: true, writable: true, value: env.window.NodeFilter });
|
||||
Object.defineProperty(globalThis, "HTMLInputElement", { configurable: true, writable: true, value: env.window.HTMLInputElement });
|
||||
const { I18nProvider } = await import("../../application/i18n/I18nProvider.tsx");
|
||||
const { SftpConflictDialog } = await import("./SftpConflictDialog.tsx");
|
||||
const renderer = await createDomRenderer(env.document);
|
||||
const resolved: Array<{ id: string; action: string }> = [];
|
||||
t.after(async () => {
|
||||
await renderer.unmount();
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
Object.defineProperty(globalThis, "MutationObserver", { configurable: true, writable: true, value: previousMutationObserver });
|
||||
Object.defineProperty(globalThis, "NodeFilter", { configurable: true, writable: true, value: previousNodeFilter });
|
||||
Object.defineProperty(globalThis, "HTMLInputElement", { configurable: true, writable: true, value: previousHTMLInputElement });
|
||||
env.cleanup();
|
||||
});
|
||||
|
||||
await renderer.render(React.createElement(
|
||||
I18nProvider,
|
||||
{ locale: "en" },
|
||||
React.createElement(SftpConflictDialog, {
|
||||
conflicts: [{
|
||||
transferId: scenario.name,
|
||||
fileName: "test.txt",
|
||||
sourcePath: "/src/test.txt",
|
||||
targetPath: "/dst/test.txt",
|
||||
isDirectory: scenario.isDirectory,
|
||||
existingType: scenario.existingType,
|
||||
existingSize: 1,
|
||||
newSize: 2,
|
||||
existingModified: 1,
|
||||
newModified: 2,
|
||||
}],
|
||||
onResolve: (id, action) => resolved.push({ id, action }),
|
||||
formatFileSize: (size: number) => `${size} B`,
|
||||
}),
|
||||
));
|
||||
await flushEffects();
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
|
||||
const focused = env.document.activeElement;
|
||||
assert.equal(focused?.textContent, scenario.label);
|
||||
assert.ok(focused, "a conflict action should receive focus");
|
||||
await dispatchDomEvent(focused, new env.window.MouseEvent("click", { bubbles: true }));
|
||||
assert.deepEqual(resolved, [{ id: scenario.name, action: scenario.action }]);
|
||||
});
|
||||
}
|
||||
@@ -107,6 +107,11 @@ const SftpConflictDialogInner: React.FC<SftpConflictDialogProps> = ({ conflicts,
|
||||
const conflict = conflicts[0]; // Handle first conflict
|
||||
const currentCanMerge = conflict?.isDirectory === true && conflict.existingType === 'directory';
|
||||
const currentCanReplace = conflict ? canReplaceConflict(conflict) : false;
|
||||
const preferredActionRef = currentCanMerge
|
||||
? mergeButtonRef
|
||||
: currentCanReplace && conflict?.existingType
|
||||
? replaceButtonRef
|
||||
: duplicateButtonRef;
|
||||
|
||||
useEffect(() => {
|
||||
const currentConflictId = conflict?.transferId;
|
||||
@@ -114,11 +119,7 @@ const SftpConflictDialogInner: React.FC<SftpConflictDialogProps> = ({ conflicts,
|
||||
previousConflictIdRef.current = currentConflictId;
|
||||
if (!currentConflictId || !previousConflictId || currentConflictId === previousConflictId) return;
|
||||
|
||||
const nextAction = currentCanMerge
|
||||
? mergeButtonRef.current
|
||||
: currentCanReplace
|
||||
? replaceButtonRef.current
|
||||
: duplicateButtonRef.current;
|
||||
const nextAction = preferredActionRef.current;
|
||||
if (!nextAction) return;
|
||||
|
||||
// If the previously focused action disappears or becomes disabled,
|
||||
@@ -130,7 +131,7 @@ const SftpConflictDialogInner: React.FC<SftpConflictDialogProps> = ({ conflicts,
|
||||
}
|
||||
const timer = globalThis.setTimeout(() => nextAction.focus(), 0);
|
||||
return () => globalThis.clearTimeout(timer);
|
||||
}, [conflict?.transferId, currentCanMerge, currentCanReplace]);
|
||||
}, [conflict?.transferId, preferredActionRef]);
|
||||
|
||||
if (!conflict) return null;
|
||||
|
||||
@@ -160,6 +161,10 @@ const SftpConflictDialogInner: React.FC<SftpConflictDialogProps> = ({ conflicts,
|
||||
<DialogContent
|
||||
className="gap-4 p-5 sm:max-w-[600px] sm:p-6"
|
||||
aria-describedby={describedBy}
|
||||
onOpenAutoFocus={(event) => {
|
||||
event.preventDefault();
|
||||
preferredActionRef.current?.focus();
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="space-y-1.5 pr-8">
|
||||
<DialogTitle
|
||||
@@ -262,7 +267,6 @@ const SftpConflictDialogInner: React.FC<SftpConflictDialogProps> = ({ conflicts,
|
||||
size="sm"
|
||||
onClick={() => handleAction('merge')}
|
||||
disabled={!canMerge}
|
||||
autoFocus={presentation.showDirectoryReplaceWarning}
|
||||
className="min-w-24 border border-primary"
|
||||
>
|
||||
{t('sftp.conflict.action.merge')}
|
||||
|
||||
Reference in new issue
Block a user