fix(nextcloud-files): IN-07 Ablegen auf offene Dialoge und Menues wird ignoriert
- bei offenem Dialog oder Menue (oder einem Ziel darin) zeigt die Ansicht keine Ablage-Markierung und laedt nichts hoch; das Ereignis wird trotzdem abgefangen, damit der Browser die Datei nicht selbst oeffnet - ein bereits behandeltes Ablegen (defaultPrevented) wird nicht hochgeladen Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
act,
|
act,
|
||||||
cleanup,
|
cleanup,
|
||||||
|
createEvent,
|
||||||
fireEvent,
|
fireEvent,
|
||||||
render as rtlRender,
|
render as rtlRender,
|
||||||
screen,
|
screen,
|
||||||
@@ -549,6 +550,35 @@ describe('FileBrowser — Ziehen und Ablegen', () => {
|
|||||||
expect(screen.queryByTestId('nc-files-drop-overlay')).toBeNull();
|
expect(screen.queryByTestId('nc-files-drop-overlay')).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Ablegen bei offenem Dialog wird ignoriert, ohne dass der Browser die Datei oeffnet (IN-07)', async () => {
|
||||||
|
await mount();
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Neuer Ordner' }));
|
||||||
|
const input = await screen.findByLabelText('Name des Ordners');
|
||||||
|
const dt = files([new File(['x'], 'neu.txt')]);
|
||||||
|
fireEvent.dragEnter(input, { dataTransfer: dt });
|
||||||
|
expect(screen.queryByTestId('nc-files-drop-overlay')).toBeNull();
|
||||||
|
const over = createEvent.dragOver(input, { dataTransfer: dt });
|
||||||
|
fireEvent(input, over);
|
||||||
|
expect(over.defaultPrevented).toBe(true);
|
||||||
|
const drop = createEvent.drop(input, { dataTransfer: dt });
|
||||||
|
fireEvent(input, drop);
|
||||||
|
expect(drop.defaultPrevented).toBe(true);
|
||||||
|
await act(async () => {});
|
||||||
|
expect(mockUpload).not.toHaveBeenCalled();
|
||||||
|
expect(screen.queryByTestId('nc-files-drop-overlay')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ein schon behandeltes Ablegen (defaultPrevented) wird nicht hochgeladen', async () => {
|
||||||
|
await mount();
|
||||||
|
const dt = files([new File(['x'], 'neu.txt')]);
|
||||||
|
const row = screen.getByRole('row', { name: 'Projekte' });
|
||||||
|
const drop = createEvent.drop(row, { dataTransfer: dt });
|
||||||
|
drop.preventDefault();
|
||||||
|
fireEvent(row, drop);
|
||||||
|
await act(async () => {});
|
||||||
|
expect(mockUpload).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
it('ein Ziehen ohne Dateien (markierter Text) wird ignoriert', async () => {
|
it('ein Ziehen ohne Dateien (markierter Text) wird ignoriert', async () => {
|
||||||
await mount();
|
await mount();
|
||||||
const dt = { types: ['text/plain'], files: [], items: [] };
|
const dt = { types: ['text/plain'], files: [], items: [] };
|
||||||
|
|||||||
@@ -417,9 +417,18 @@ export function FileBrowser({
|
|||||||
return () => window.removeEventListener('beforeunload', warn);
|
return () => window.removeEventListener('beforeunload', warn);
|
||||||
}, [anyActive]);
|
}, [anyActive]);
|
||||||
|
|
||||||
// Ziehen und Ablegen: am Fenster, mit Tiefenzaehler; nur Ziehen mit Dateien zaehlt.
|
// Ziehen und Ablegen: am Fenster, mit Tiefenzaehler; nur Ziehen mit Dateien zaehlt. Ist ein
|
||||||
|
// Dialog oder Menue offen (oder liegt das Ziel darin), wird nichts abgelegt (IN-07): der
|
||||||
|
// Browser darf die Datei dann aber auch nicht selbst oeffnen.
|
||||||
|
const dialogOpenRef = useRef(false);
|
||||||
|
dialogOpenRef.current = dialog !== null || menu !== null;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let depth = 0;
|
let depth = 0;
|
||||||
|
const blocked = (e: DragEvent): boolean => {
|
||||||
|
if (dialogOpenRef.current) return true;
|
||||||
|
const el = e.target instanceof Element ? e.target : null;
|
||||||
|
return !!el?.closest('[role="dialog"], [role="alertdialog"], [role="menu"]');
|
||||||
|
};
|
||||||
const targetOf = (e: DragEvent): string => {
|
const targetOf = (e: DragEvent): string => {
|
||||||
const el = e.target instanceof Element ? e.target.closest('[data-drop-folder]') : null;
|
const el = e.target instanceof Element ? e.target.closest('[data-drop-folder]') : null;
|
||||||
return el?.getAttribute('data-drop-folder') ?? pathRef.current;
|
return el?.getAttribute('data-drop-folder') ?? pathRef.current;
|
||||||
@@ -427,11 +436,16 @@ export function FileBrowser({
|
|||||||
const onEnter = (e: DragEvent) => {
|
const onEnter = (e: DragEvent) => {
|
||||||
if (!hasFiles(e.dataTransfer)) return;
|
if (!hasFiles(e.dataTransfer)) return;
|
||||||
depth += 1;
|
depth += 1;
|
||||||
setDropTarget(targetOf(e));
|
setDropTarget(blocked(e) ? null : targetOf(e));
|
||||||
};
|
};
|
||||||
const onOver = (e: DragEvent) => {
|
const onOver = (e: DragEvent) => {
|
||||||
if (!hasFiles(e.dataTransfer)) return;
|
if (!hasFiles(e.dataTransfer)) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (blocked(e)) {
|
||||||
|
if (e.dataTransfer) e.dataTransfer.dropEffect = 'none';
|
||||||
|
setDropTarget(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
|
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
|
||||||
const target = targetOf(e);
|
const target = targetOf(e);
|
||||||
setDropTarget((prev) => (prev === target ? prev : target));
|
setDropTarget((prev) => (prev === target ? prev : target));
|
||||||
@@ -443,10 +457,13 @@ export function FileBrowser({
|
|||||||
};
|
};
|
||||||
const onDrop = (e: DragEvent) => {
|
const onDrop = (e: DragEvent) => {
|
||||||
if (!hasFiles(e.dataTransfer) || !e.dataTransfer) return;
|
if (!hasFiles(e.dataTransfer) || !e.dataTransfer) return;
|
||||||
|
// Ein Bestandteil der Seite hat das Ablegen schon selbst behandelt.
|
||||||
|
const handled = e.defaultPrevented;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
depth = 0;
|
depth = 0;
|
||||||
const target = targetOf(e);
|
|
||||||
setDropTarget(null);
|
setDropTarget(null);
|
||||||
|
if (handled || blocked(e)) return;
|
||||||
|
const target = targetOf(e);
|
||||||
const snapshot = snapshotDrop(e.dataTransfer);
|
const snapshot = snapshotDrop(e.dataTransfer);
|
||||||
void collectDrop(snapshot).then((result) => {
|
void collectDrop(snapshot).then((result) => {
|
||||||
if (result.problem) {
|
if (result.problem) {
|
||||||
|
|||||||
Reference in New Issue
Block a user