Release v1.26.13: Fix drag-and-drop file opening in tab & improve magazine dropzone reliability
This commit is contained in:
parent
11142842c2
commit
30e01a5cec
11
CHANGELOG.md
11
CHANGELOG.md
@ -5,6 +5,17 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [1.26.13] — 2026-09-10
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **App-Wide Drag-and-Drop Safety Net & Dropzone Reliability.**
|
||||||
|
- Installed an eager app-wide capture-phase `dragover` / `drop` safety net in `utils.js` that intercepts file drops on `window` and `document` to guarantee the browser never navigates away to open a dropped PDF in a new tab.
|
||||||
|
- Improved `#mag-dropzone` drag event handling with nested element drag-depth tracking (`dragCounter`), preventing dragover class flickering when hovering over dropzone titles, icons, or buttons.
|
||||||
|
- Direct file passing in `magCreate(file)`, removing programmatic `<input>` DataTransfer mutation to eliminate double invocation and race conditions during drag-and-drop.
|
||||||
|
- Cleaned up duplicate variable scope declarations in `renderReview()` in `magazine.js`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [1.26.12] — 2026-09-10
|
## [1.26.12] — 2026-09-10
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
70
static/dist/main.min.js
vendored
70
static/dist/main.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,11 +1,11 @@
|
|||||||
// ── Init ──────────────────────────────────────────────────────────────────
|
// ── Init ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
initBenchmarkSampleControls();
|
if (typeof initBenchmarkSampleControls === 'function') initBenchmarkSampleControls();
|
||||||
// Sync visible preview-text input with hidden benchmark-sample-text
|
// Sync visible preview-text input with hidden benchmark-sample-text
|
||||||
(function syncPreviewInput() {
|
(function syncPreviewInput() {
|
||||||
const visible = $('vl-preview-sample');
|
const visible = $('vl-preview-sample');
|
||||||
const hidden = $('benchmark-sample-text');
|
const hidden = $('benchmark-sample-text');
|
||||||
if (!visible) return;
|
if (!visible || typeof BENCHMARK_SAMPLE_STORAGE_KEY === 'undefined') return;
|
||||||
const stored = localStorage.getItem(BENCHMARK_SAMPLE_STORAGE_KEY);
|
const stored = localStorage.getItem(BENCHMARK_SAMPLE_STORAGE_KEY);
|
||||||
visible.value = stored || DEFAULT_BENCHMARK_SAMPLE_TEXT;
|
visible.value = stored || DEFAULT_BENCHMARK_SAMPLE_TEXT;
|
||||||
if (hidden) hidden.value = visible.value;
|
if (hidden) hidden.value = visible.value;
|
||||||
@ -19,12 +19,14 @@ initBenchmarkSampleControls();
|
|||||||
(function syncPreviewLangBtn() {
|
(function syncPreviewLangBtn() {
|
||||||
const ta = $('vl-preview-sample');
|
const ta = $('vl-preview-sample');
|
||||||
const btn = $('vl-preview-lang-btn');
|
const btn = $('vl-preview-lang-btn');
|
||||||
if (!ta || !btn) return;
|
if (!ta || !btn || typeof VL_SAMPLE_TEXTS === 'undefined') return;
|
||||||
const cur = ta.value.trim();
|
const cur = ta.value.trim();
|
||||||
const matched = Object.keys(VL_SAMPLE_TEXTS).find(l => VL_SAMPLE_TEXTS[l] === cur) || 'EN';
|
const matched = Object.keys(VL_SAMPLE_TEXTS).find(l => VL_SAMPLE_TEXTS[l] === cur) || 'EN';
|
||||||
const cc = (_VL_LANG_FLAG[matched] || 'gb').toLowerCase();
|
const flags = typeof _VL_LANG_FLAG !== 'undefined' ? _VL_LANG_FLAG : {};
|
||||||
|
const labels = typeof LANGUAGE_LABELS !== 'undefined' ? LANGUAGE_LABELS : {};
|
||||||
|
const cc = (flags[matched] || 'gb').toLowerCase();
|
||||||
btn.innerHTML = `<span class="fi fi-${cc}"></span>`;
|
btn.innerHTML = `<span class="fi fi-${cc}"></span>`;
|
||||||
btn.title = (LANGUAGE_LABELS[matched] || matched) + ' — click to change sample language';
|
btn.title = (labels[matched] || matched) + ' — click to change sample language';
|
||||||
btn.dataset.lang = matched;
|
btn.dataset.lang = matched;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
@ -213,13 +213,15 @@ function magShowDroppedFile(file) {
|
|||||||
function installGlobalMagDropGuard() {
|
function installGlobalMagDropGuard() {
|
||||||
if (window._magDropGuardInstalled) return;
|
if (window._magDropGuardInstalled) return;
|
||||||
window._magDropGuardInstalled = true;
|
window._magDropGuardInstalled = true;
|
||||||
window.addEventListener('dragover', (e) => {
|
const guard = (e) => {
|
||||||
|
if (e.dataTransfer && Array.from(e.dataTransfer.types || []).includes('Files')) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
|
if (e.type === 'dragover' && e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
|
||||||
});
|
}
|
||||||
window.addEventListener('drop', (e) => {
|
};
|
||||||
e.preventDefault();
|
window.addEventListener('dragenter', guard, true);
|
||||||
});
|
window.addEventListener('dragover', guard, true);
|
||||||
|
window.addEventListener('drop', guard, true);
|
||||||
}
|
}
|
||||||
installGlobalMagDropGuard();
|
installGlobalMagDropGuard();
|
||||||
|
|
||||||
@ -239,50 +241,61 @@ function setupMagDropzone() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// dragenter AND dragover both need preventDefault() -- some browsers only
|
let dragCounter = 0;
|
||||||
// arm the element as a valid drop target once dragenter itself has been
|
|
||||||
// cancelled, not just the dragover events that follow it.
|
zone.addEventListener('dragenter', (e) => {
|
||||||
const armDropTarget = (e) => {
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
dragCounter++;
|
||||||
|
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
|
||||||
|
zone.classList.add('dragover');
|
||||||
|
});
|
||||||
|
|
||||||
|
zone.addEventListener('dragover', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
|
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
|
||||||
zone.classList.add('dragover');
|
zone.classList.add('dragover');
|
||||||
};
|
});
|
||||||
zone.addEventListener('dragenter', armDropTarget);
|
|
||||||
zone.addEventListener('dragover', armDropTarget);
|
|
||||||
zone.addEventListener('dragleave', (e) => {
|
zone.addEventListener('dragleave', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
dragCounter = Math.max(0, dragCounter - 1);
|
||||||
|
if (dragCounter === 0) {
|
||||||
zone.classList.remove('dragover');
|
zone.classList.remove('dragover');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
zone.addEventListener('drop', (e) => {
|
zone.addEventListener('drop', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
dragCounter = 0;
|
||||||
zone.classList.remove('dragover');
|
zone.classList.remove('dragover');
|
||||||
const file = e.dataTransfer?.files?.[0];
|
|
||||||
|
let file = e.dataTransfer?.files?.[0];
|
||||||
|
if (!file && e.dataTransfer?.items?.[0]) {
|
||||||
|
file = e.dataTransfer.items[0].getAsFile();
|
||||||
|
}
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
if (file.type !== 'application/pdf' && !/\.pdf$/i.test(file.name)) {
|
if (file.type !== 'application/pdf' && !/\.pdf$/i.test(file.name)) {
|
||||||
return say('Only PDF files are supported', 'err');
|
return say('Only PDF files are supported', 'err');
|
||||||
}
|
}
|
||||||
// Route the dropped file through the same hidden <input> magCreate()
|
|
||||||
// already reads, so drag-and-drop and "Choose file" share one code path.
|
|
||||||
const dt = new DataTransfer();
|
|
||||||
dt.items.add(file);
|
|
||||||
input.files = dt.files;
|
|
||||||
magShowDroppedFile(file);
|
magShowDroppedFile(file);
|
||||||
magCreate();
|
magCreate(file);
|
||||||
});
|
});
|
||||||
|
|
||||||
input.addEventListener('change', () => {
|
input.addEventListener('change', () => {
|
||||||
const file = input.files?.[0];
|
const file = input.files?.[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
magShowDroppedFile(file);
|
magShowDroppedFile(file);
|
||||||
magCreate();
|
magCreate(file);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function magCreate() {
|
async function magCreate(droppedFile = null) {
|
||||||
const file = $('mag-new-file')?.files?.[0];
|
const file = droppedFile || $('mag-new-file')?.files?.[0];
|
||||||
if (!file) return say('Choose or drop a PDF file first', 'err');
|
if (!file) return say('Choose or drop a PDF file first', 'err');
|
||||||
|
|
||||||
let title = ($('mag-new-title')?.value || '').trim();
|
let title = ($('mag-new-title')?.value || '').trim();
|
||||||
@ -675,48 +688,33 @@ function renderReview() {
|
|||||||
const included = _magReviewChecked || new Set(a.blocks || []);
|
const included = _magReviewChecked || new Set(a.blocks || []);
|
||||||
const conf = confidenceLabel(a.confidence);
|
const conf = confidenceLabel(a.confidence);
|
||||||
const page = state.reviewPage;
|
const page = state.reviewPage;
|
||||||
const pageMeta = (state.layout.pages || []).find((p) => p.page === page);
|
const pageMeta = page != null ? (state.layout?.pages || []).find((p) => p.page === page) : null;
|
||||||
const pageBlocks = page == null ? [] : blocksForPages([page]);
|
|
||||||
const pageImages = page == null ? [] : (state.layout.images || [])
|
|
||||||
.filter((img) => img.page === page).sort((x, y) => (x.reading_order || 0) - (y.reading_order || 0));
|
|
||||||
const ownImages = new Set(a.images || []);
|
const ownImages = new Set(a.images || []);
|
||||||
|
|
||||||
|
const pageBlocks = page == null ? [] : blocksForPages([page]);
|
||||||
|
const pageImages = page == null ? [] : (state.layout?.images || [])
|
||||||
|
.filter((img) => img.page === page).sort((x, y) => (x.reading_order || 0) - (y.reading_order || 0));
|
||||||
// One merged, reading-order-sorted list so numbering on both sides matches
|
// One merged, reading-order-sorted list so numbering on both sides matches
|
||||||
// regardless of whether an item is text or a figure.
|
// regardless of whether an item is text or a figure.
|
||||||
const visualBlockIds = new Set(pageImages.map(i => i.source_block).filter(Boolean));
|
const visualBlockIds = new Set(pageImages.map(i => i.source_block).filter(Boolean));
|
||||||
const regions = pageBlocks.filter(b => !visualBlockIds.has(b.id)).map((b) => ({ kind: 'block', id: b.id, ro: b.reading_order, data: b }))
|
const regions = pageBlocks.filter(b => !visualBlockIds.has(b.id)).map((b) => ({ kind: 'block', id: b.id, ro: b.reading_order ?? 0, data: b }))
|
||||||
.concat(pageImages.map((img) => ({ kind: 'image', id: img.id, ro: img.reading_order || 0, data: img })))
|
.concat(pageImages.map((img) => ({ kind: 'image', id: img.id, ro: img.reading_order ?? 0, data: img })))
|
||||||
.sort((x, y) => x.ro - y.ro);
|
.sort((x, y) => x.ro - y.ro);
|
||||||
|
|
||||||
const spanIdx = state.reviewSpan.indexOf(page);
|
const spanIdx = (state.reviewSpan || []).indexOf(page);
|
||||||
const prevPage = spanIdx > 0 ? state.reviewSpan[spanIdx - 1] : null;
|
const prevPage = spanIdx > 0 ? state.reviewSpan[spanIdx - 1] : null;
|
||||||
const nextPage = spanIdx >= 0 && spanIdx < state.reviewSpan.length - 1 ? state.reviewSpan[spanIdx + 1] : null;
|
const nextPage = spanIdx >= 0 && spanIdx < (state.reviewSpan || []).length - 1 ? state.reviewSpan[spanIdx + 1] : null;
|
||||||
const issuePages = Number(state.layout?.page_count || 0);
|
|
||||||
const previousIssuePage = page > 1 ? page - 1 : null;
|
|
||||||
if (!host) return;
|
|
||||||
const page = state.reviewPage;
|
|
||||||
const pageMeta = page != null ? (state.layout.pages || []).find((p) => p.page === page) : null;
|
|
||||||
const article = state.article;
|
|
||||||
const issuePages = (state.layout || {}).page_count || '—';
|
const issuePages = (state.layout || {}).page_count || '—';
|
||||||
const nextIssuePage = page != null && page < (state.layout || {}).page_count ? page + 1 : null;
|
const nextIssuePage = page != null && page < (state.layout || {}).page_count ? page + 1 : null;
|
||||||
const prevIssuePage = page != null && page > 1 ? page - 1 : null;
|
const prevIssuePage = page != null && page > 1 ? page - 1 : null;
|
||||||
|
|
||||||
const included = new Set(article ? article.blocks || [] : []);
|
|
||||||
const ownImages = new Set(article ? article.images || [] : []);
|
|
||||||
|
|
||||||
const pageBlocks = (state.layout.blocks || []).filter((b) => b.page === page);
|
|
||||||
const pageImages = (state.layout.images || []).filter((i) => i.page === page);
|
|
||||||
const regions = [
|
|
||||||
...pageBlocks.map((b) => ({ kind: 'block', id: b.id, data: b, ro: b.reading_order ?? 0 })),
|
|
||||||
...pageImages.map((i) => ({ kind: 'image', id: i.id, data: i, ro: i.reading_order ?? 0 })),
|
|
||||||
].sort((a, b) => a.ro - b.ro);
|
|
||||||
|
|
||||||
let html = '';
|
let html = '';
|
||||||
html += '<div class="mag-review-pagenav">' +
|
html += '<div class="mag-review-pagenav">' +
|
||||||
'<div class="mag-review-pagenav-group" role="group" aria-label="Review pages">' +
|
'<div class="mag-review-pagenav-group" role="group" aria-label="Review pages">' +
|
||||||
'<button type="button" class="btn-sm" aria-label="Previous page" ' + (state.reviewPageIdx > 0 ? '' : 'disabled') +
|
'<button type="button" class="btn-sm" aria-label="Previous page" ' + (state.reviewPageIdx > 0 ? '' : 'disabled') +
|
||||||
' onclick="magReviewStepPage(-1)"><span class="mdi mdi-chevron-left" aria-hidden="true"></span></button>' +
|
' onclick="magReviewStepPage(-1)"><span class="mdi mdi-chevron-left" aria-hidden="true"></span></button>' +
|
||||||
'<strong class="mag-review-page-number">Page ' + (page ?? '—') + '</strong>' +
|
'<strong class="mag-review-page-number">Page ' + (page ?? '—') + '</strong>' +
|
||||||
'<button type="button" class="btn-sm" aria-label="Next page" ' + (state.reviewPageIdx < (article?.pages || []).length - 1 ? '' : 'disabled') +
|
'<button type="button" class="btn-sm" aria-label="Next page" ' + (state.reviewPageIdx < (a?.pages || []).length - 1 ? '' : 'disabled') +
|
||||||
' onclick="magReviewStepPage(1)"><span class="mdi mdi-chevron-right" aria-hidden="true"></span></button></div>' +
|
' onclick="magReviewStepPage(1)"><span class="mdi mdi-chevron-right" aria-hidden="true"></span></button></div>' +
|
||||||
'<div class="mag-review-pagenav-group" role="group" aria-label="Whole magazine pages">' +
|
'<div class="mag-review-pagenav-group" role="group" aria-label="Whole magazine pages">' +
|
||||||
'<button type="button" class="btn-sm" aria-label="Previous magazine page" ' + (prevIssuePage ? '' : 'disabled') +
|
'<button type="button" class="btn-sm" aria-label="Previous magazine page" ' + (prevIssuePage ? '' : 'disabled') +
|
||||||
@ -745,7 +743,6 @@ function renderReview() {
|
|||||||
'</div>';
|
'</div>';
|
||||||
|
|
||||||
html += '<div class="mag-review-fields">';
|
html += '<div class="mag-review-fields">';
|
||||||
const a = state.article || {};
|
|
||||||
html += '<div class="mag-review-field-row">' +
|
html += '<div class="mag-review-field-row">' +
|
||||||
'<label>Title<span class="mag-review-field-control"><input id="mag-rev-title" type="text" value="' + esc(a.title) + '"><button type="button" class="btn-sm" onclick="magReviewToggleDraw(\'title\')"><span class="mdi mdi-vector-square-edit"></span> Pick from page</button></span></label>' +
|
'<label>Title<span class="mag-review-field-control"><input id="mag-rev-title" type="text" value="' + esc(a.title) + '"><button type="button" class="btn-sm" onclick="magReviewToggleDraw(\'title\')"><span class="mdi mdi-vector-square-edit"></span> Pick from page</button></span></label>' +
|
||||||
'<label>Author<span class="mag-review-field-control"><input id="mag-rev-author" type="text" value="' + esc(a.author) + '"><button type="button" class="btn-sm" onclick="magReviewToggleDraw(\'author\')"><span class="mdi mdi-vector-square-edit"></span> Pick from page</button></span></label>' +
|
'<label>Author<span class="mag-review-field-control"><input id="mag-rev-author" type="text" value="' + esc(a.author) + '"><button type="button" class="btn-sm" onclick="magReviewToggleDraw(\'author\')"><span class="mdi mdi-vector-square-edit"></span> Pick from page</button></span></label>' +
|
||||||
@ -753,7 +750,6 @@ function renderReview() {
|
|||||||
'<span id="mag-draw-hint" class="muted"></span>';
|
'<span id="mag-draw-hint" class="muted"></span>';
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
const conf = confidenceLabel(article ? article.confidence : 1.0);
|
|
||||||
html += '<span class="mag-review-conf ' + conf.cls + '">' + esc(conf.text) + '</span>';
|
html += '<span class="mag-review-conf ' + conf.cls + '">' + esc(conf.text) + '</span>';
|
||||||
html += '<p class="muted">Untick anything that is not part of this article — a sidebar, an advert, or a neighbouring piece. Only ticked text is read aloud. Click a numbered region on the page, or its matching entry on the right, to line the two up. A region labelled with another article belongs to a different piece sharing this page. Draw a missing text or image region below.</p>';
|
html += '<p class="muted">Untick anything that is not part of this article — a sidebar, an advert, or a neighbouring piece. Only ticked text is read aloud. Click a numbered region on the page, or its matching entry on the right, to line the two up. A region labelled with another article belongs to a different piece sharing this page. Draw a missing text or image region below.</p>';
|
||||||
|
|
||||||
|
|||||||
@ -1392,6 +1392,24 @@ if (typeof MutationObserver !== 'undefined') {
|
|||||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => initLangSelects(document));
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => initLangSelects(document));
|
||||||
else initLangSelects(document);
|
else initLangSelects(document);
|
||||||
|
|
||||||
|
// App-Wide Drag and Drop Safety Net:
|
||||||
|
// Prevents the browser from navigating away to a dropped file (e.g. opening PDF in a new tab)
|
||||||
|
// if a drop occurs outside an active dropzone or on elements without custom drop handling.
|
||||||
|
(function installGlobalAppDropGuard() {
|
||||||
|
if (root._appDropGuardInstalled) return;
|
||||||
|
root._appDropGuardInstalled = true;
|
||||||
|
const preventDefaultDrop = (e) => {
|
||||||
|
if (e.dataTransfer && Array.from(e.dataTransfer.types || []).includes('Files')) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (e.type === 'dragover') {
|
||||||
|
e.dataTransfer.dropEffect = 'copy';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('dragover', preventDefaultDrop, true);
|
||||||
|
window.addEventListener('drop', preventDefaultDrop, true);
|
||||||
|
})();
|
||||||
|
|
||||||
Object.assign(root, {
|
Object.assign(root, {
|
||||||
$, avatarColor, toast, confirmDialog, status, ensureStatusBar, updateStatusBar,
|
$, avatarColor, toast, confirmDialog, status, ensureStatusBar, updateStatusBar,
|
||||||
escHtml, debounce, logErr, ttvcIsLowMemoryDevice, fmtDuration, loadingMarkup,
|
escHtml, debounce, logErr, ttvcIsLowMemoryDevice, fmtDuration, loadingMarkup,
|
||||||
|
|||||||
@ -218,3 +218,35 @@ test('drawing a chart links the crop and excludes enclosed narration on approval
|
|||||||
await page.getByRole('button', { name: 'Approve', exact: true }).click();
|
await page.getByRole('button', { name: 'Approve', exact: true }).click();
|
||||||
expect((await request).postDataJSON()).toMatchObject({ images: ['manual-chart'], blocks: ['b1'] });
|
expect((await request).postDataJSON()).toMatchObject({ images: ['manual-chart'], blocks: ['b1'] });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('dropping a PDF file onto mag-dropzone triggers creation without browser navigation', async ({ page }) => {
|
||||||
|
await mockBackend(page);
|
||||||
|
let uploadCalled = false;
|
||||||
|
await page.route('**/api/magazine/docs', route => {
|
||||||
|
uploadCalled = true;
|
||||||
|
return route.fulfill({ json: { id: DOC_ID } });
|
||||||
|
});
|
||||||
|
await page.route(`**/api/reader/docs/${DOC_ID}/source*`, route => route.fulfill({ json: { ok: true } }));
|
||||||
|
|
||||||
|
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
||||||
|
await page.waitForSelector('#app-skeleton', { state: 'detached' });
|
||||||
|
await navTo(page, 's-magazine');
|
||||||
|
|
||||||
|
const dropzone = page.locator('#mag-dropzone');
|
||||||
|
await expect(dropzone).toBeVisible();
|
||||||
|
|
||||||
|
const dataTransfer = await page.evaluateHandle(() => {
|
||||||
|
const dt = new DataTransfer();
|
||||||
|
const file = new File(['%PDF-1.4 dummy pdf content'], 'ct_KI_Wissen_2026.pdf', { type: 'application/pdf' });
|
||||||
|
dt.items.add(file);
|
||||||
|
return dt;
|
||||||
|
});
|
||||||
|
|
||||||
|
await dropzone.dispatchEvent('dragenter', { dataTransfer });
|
||||||
|
await dropzone.dispatchEvent('dragover', { dataTransfer });
|
||||||
|
await dropzone.dispatchEvent('drop', { dataTransfer });
|
||||||
|
|
||||||
|
await page.waitForTimeout(300);
|
||||||
|
expect(uploadCalled).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user