import { test, expect } from './coverage-helpers'; import { apiLogin, apiCreateFolder, apiUploadFile, SAMPLE_FILES } from '../scenarios/helpers'; /** * Deeper files-page coverage (the largest source file): list/grid view toggle, * column sorting, group-by lanes, breadcrumb navigation, and copy-via-dialog. */ test.beforeEach(async ({ page }) => { await apiLogin(page); }); function uniq(p: string): string { return `${p}-${Date.now()}-${Math.floor(Math.random() * 1e6)}`; } test('sort columns and toggle list/grid views', async ({ page }) => { const folder = await apiCreateFolder(page, uniq('SortHost')); await apiUploadFile(page, SAMPLE_FILES.text(), folder.id); await apiUploadFile(page, SAMPLE_FILES.json(), folder.id); await page.goto(`/files/${folder.id}`); await expect(page.getByTestId(SAMPLE_FILES.text().name)).toBeVisible({ timeout: 15_000 }); await page.getByTestId('display-mode-view-list-btn').click(); // Column sort buttons live in the list-view header. await page.getByTestId('files-sort-name-btn').click({ timeout: 5_000 }).catch(() => {}); await page.getByTestId('files-sort-size-btn').click({ timeout: 5_000 }).catch(() => {}); await page.getByTestId('files-sort-modified_at-btn').click({ timeout: 5_000 }).catch(() => {}); await page.getByTestId('display-mode-view-grid-btn').click(); }); test('sort by every column and group by every dimension', async ({ page }) => { const folder = await apiCreateFolder(page, uniq('Dims')); await apiUploadFile(page, SAMPLE_FILES.text(), folder.id); await apiUploadFile(page, SAMPLE_FILES.png(), folder.id); await apiUploadFile(page, SAMPLE_FILES.pdf(), folder.id); await page.goto(`/files/${folder.id}`); await expect(page.getByTestId(SAMPLE_FILES.text().name)).toBeVisible({ timeout: 15_000 }); // List view exposes the column-sort buttons. await page.getByTestId('display-mode-view-list-btn').click(); for (const col of ['name', 'owner', 'type', 'size', 'modified_at']) { await page.getByTestId(`files-sort-${col}-btn`).click({ timeout: 3_000 }).catch(() => {}); } // Flip the sort direction. await page.getByTestId('display-mode-sort-direction-btn').click({ timeout: 3_000 }).catch(() => {}); // Cycle through every group-by dimension. for (const g of ['type', 'size', 'modifiedAt', 'createdAt']) { await page.getByTestId('display-mode-groupby-btn').click({ timeout: 3_000 }).catch(() => {}); await page.getByTestId(`display-mode-groupby-${g}-item`).click({ timeout: 3_000 }).catch(() => {}); } }); test('group files by type', async ({ page }) => { const folder = await apiCreateFolder(page, uniq('GroupHost')); await apiUploadFile(page, SAMPLE_FILES.text(), folder.id); await apiUploadFile(page, SAMPLE_FILES.png(), folder.id); await page.goto(`/files/${folder.id}`); await expect(page.getByTestId(SAMPLE_FILES.text().name)).toBeVisible({ timeout: 15_000 }); await page.getByTestId('display-mode-groupby-btn').click(); await page.getByTestId('display-mode-groupby-type-item').click(); // The grouped (swimlane) view now renders; items remain visible. await expect(page.getByTestId(SAMPLE_FILES.text().name)).toBeVisible(); }); test('file context menu: favorite, copy, open-parent', async ({ page }) => { const folder = await apiCreateFolder(page, uniq('Ctx')); await apiCreateFolder(page, uniq('CtxDest')); const f = SAMPLE_FILES.text(); await apiUploadFile(page, f, folder.id); await page.goto(`/files/${folder.id}`); await expect(page.getByTestId(f.name)).toBeVisible({ timeout: 15_000 }); // Favorite the file from its context menu. await page.getByTestId(f.name).click({ button: 'right' }); await page.getByTestId('files-ctx-favorite-item').click(); await expect(page.getByTestId('files-context-menu')).toHaveCount(0); // Copy → opens the move dialog (copy mode); cancel. await page.getByTestId(f.name).click({ button: 'right' }); await page.getByTestId('files-ctx-copy-item').click(); await expect(page.getByTestId('move-dialog')).toBeVisible({ timeout: 15_000 }); await page.getByTestId('move-dialog-cancel-btn').click(); // Open-parent navigates to the file's parent folder. await page.getByTestId(f.name).click({ button: 'right' }); await page.getByTestId('files-ctx-open-parent-item').click(); await expect(page).toHaveURL(/\/files\//, { timeout: 15_000 }); }); test('download a folder as a zip via the context menu', async ({ page }) => { const folderName = uniq('Zip'); const folder = await apiCreateFolder(page, folderName); await apiUploadFile(page, SAMPLE_FILES.text(), folder.id); await page.goto('/files'); await expect(page.getByTestId(folderName)).toBeVisible({ timeout: 15_000 }); await page.getByTestId(folderName).click({ button: 'right' }); const downloadPromise = page.waitForEvent('download', { timeout: 10_000 }).catch(() => null); await page.getByTestId('files-ctx-download-zip-item').click(); await downloadPromise; }); test('deep-link ?file= opens the viewer', async ({ page }) => { const folder = await apiCreateFolder(page, uniq('DeepLink')); const f = SAMPLE_FILES.markdown(); await apiUploadFile(page, f, folder.id); await page.goto(`/files/${folder.id}`); await expect(page.getByTestId(f.name)).toBeVisible({ timeout: 15_000 }); // Extract the file id straight off the row — ResourceList tags every // `.file-item` with `data-item-id={item.id}`. The pre-migration // approach read `files-file-share-{id}` off a per-row share button // that no longer exists (Share moved into the context menu). const fileId = await page .locator(`.file-item[data-testid="${f.name}"]`) .first() .getAttribute('data-item-id'); if (!fileId) throw new Error(`could not resolve file id for ${f.name}`); await page.goto(`/files/${folder.id}?file=${fileId}`); await expect(page.getByTestId('file-viewer-dialog')).toBeVisible({ timeout: 15_000 }); await page.getByTestId('file-viewer-close-btn').click(); }); test('download a file via the context menu', async ({ page }) => { const folder = await apiCreateFolder(page, uniq('Dl')); const f = SAMPLE_FILES.text(); await apiUploadFile(page, f, folder.id); await page.goto(`/files/${folder.id}`); await expect(page.getByTestId(f.name)).toBeVisible({ timeout: 15_000 }); await page.getByTestId(f.name).click({ button: 'right' }); const downloadPromise = page.waitForEvent('download', { timeout: 10_000 }).catch(() => null); await page.getByTestId('files-ctx-download-item').click(); const download = await downloadPromise; if (download) expect(download.suggestedFilename().length).toBeGreaterThan(0); }); test('upload a folder via the hidden folder input', async ({ page }) => { const folder = await apiCreateFolder(page, uniq('FolderUp')); await page.goto(`/files/${folder.id}`); await expect(page.getByTestId('files-upload-folder-input')).toBeAttached({ timeout: 15_000 }); // webkitdirectory input — set a couple of nested files to exercise the // folder-upload handler. await page .getByTestId('files-upload-folder-input') .setInputFiles([ { name: 'up/a.txt', mimeType: 'text/plain', buffer: Buffer.from('a') }, { name: 'up/b.txt', mimeType: 'text/plain', buffer: Buffer.from('b') }, ]) .catch(() => {}); await page.waitForTimeout(1500); // The page stays functional whether or not the upload fully completes. await expect(page.getByTestId('files-upload-btn')).toBeVisible(); }); test('drag a file onto a subfolder to move it', async ({ page }) => { const parent = await apiCreateFolder(page, uniq('DragHost')); const destName = uniq('DragDest'); await apiCreateFolder(page, destName, parent.id); const f = SAMPLE_FILES.text(); await apiUploadFile(page, f, parent.id); await page.goto(`/files/${parent.id}`); await expect(page.getByTestId(f.name)).toBeVisible({ timeout: 15_000 }); await expect(page.getByTestId(destName)).toBeVisible(); // HTML5 drag-and-drop: the row is draggable and the folder is a drop target. await page.getByTestId(f.name).dragTo(page.getByTestId(destName)); // The file moved into the destination folder. await expect(page.getByTestId(f.name)).toHaveCount(0, { timeout: 15_000 }); }); test('open a folder with the keyboard (focus + Enter)', async ({ page }) => { const parent = await apiCreateFolder(page, uniq('KeyNav')); const childName = uniq('KeyChild'); const child = await apiCreateFolder(page, childName, parent.id); await page.goto(`/files/${parent.id}`); await expect(page.getByTestId(childName)).toBeVisible({ timeout: 15_000 }); // Focus the folder row and press Enter to open it (covers the keydown path). await page.getByTestId(childName).focus(); await page.keyboard.press('Enter'); // The URL ends with the child id (nested under the parent path). await expect(page).toHaveURL(new RegExp(`${child.id}$`), { timeout: 15_000 }); }); test('keyboard select-all and escape in the files list', async ({ page }) => { const folder = await apiCreateFolder(page, uniq('Keys')); await apiCreateFolder(page, uniq('k1'), folder.id); await apiCreateFolder(page, uniq('k2'), folder.id); await page.goto(`/files/${folder.id}`); await expect(page.locator('.files-page')).toBeVisible({ timeout: 15_000 }); await page.locator('.files-page').click({ position: { x: 5, y: 5 } }); await page.keyboard.press('Control+a'); await expect(page.getByTestId('resource-list-batch-close-btn')).toBeVisible({ timeout: 5_000 }).catch(() => {}); await page.keyboard.press('Escape'); }); test('breadcrumb navigates back to home', async ({ page }) => { const folderName = uniq('Crumb'); const folder = await apiCreateFolder(page, folderName); await page.goto(`/files/${folder.id}`); // Breadcrumb root chip leaves the subfolder for the root listing. Bare /files // canonicalizes to the user's drive root, where the just-created folder lives. // Testid changed to `folder-breadcrumb-root-link` when the breadcrumb was // extracted into the shared `` component (used by /files // AND /search) — the /files-specific `files-breadcrumb-*` prefix retired. await page.getByTestId('folder-breadcrumb-root-link').click(); await expect(page).not.toHaveURL(new RegExp(folder.id)); await expect(page.getByTestId(folderName)).toBeVisible({ timeout: 15_000 }); }); test('open an image in the viewer and use the zoom controls', async ({ page }) => { const folder = await apiCreateFolder(page, uniq('ImgView')); const img = SAMPLE_FILES.png(); await apiUploadFile(page, img, folder.id); await page.goto(`/files/${folder.id}`); await expect(page.getByTestId(img.name)).toBeVisible({ timeout: 15_000 }); await page.getByTestId(img.name).click({ button: 'right' }); await page.getByTestId('files-ctx-file-open-item').click(); await expect(page.getByTestId('file-viewer-dialog')).toBeVisible({ timeout: 15_000 }); // Zoom controls render only for images. await page.getByTestId('file-viewer-zoom-in-btn').click({ timeout: 3_000 }).catch(() => {}); await page.getByTestId('file-viewer-zoom-out-btn').click({ timeout: 3_000 }).catch(() => {}); await page.getByTestId('file-viewer-zoom-reset-btn').click({ timeout: 3_000 }).catch(() => {}); // The download + open-in-new-tab links are present in the viewer toolbar. await expect(page.getByTestId('file-viewer-download-link')).toBeVisible(); await expect(page.getByTestId('file-viewer-open-new-tab-link')).toBeVisible(); await page.getByTestId('file-viewer-close-btn').click(); await expect(page.getByTestId('file-viewer-dialog')).toHaveCount(0); }); test('copy a folder into another (copy mode keeps the source)', async ({ page }) => { const srcName = uniq('CopySrc'); await apiCreateFolder(page, srcName); const dest = await apiCreateFolder(page, uniq('CopyDest')); await page.goto('/files'); await expect(page.getByTestId(srcName)).toBeVisible({ timeout: 15_000 }); await page.getByTestId(srcName).click({ button: 'right' }); await page.getByTestId('files-ctx-copy-item').click(); await expect(page.getByTestId('move-dialog')).toBeVisible(); await page.getByTestId(`move-dialog-folder-${dest.id}`).click(); await page.getByTestId('move-dialog-confirm-btn').click(); // MoveDialog closes only AFTER `await copyFolders(...)` resolves, so // waiting for its disappearance is equivalent to waiting for the // batch-copy request to complete. Without this the navigation to // /files/{dest.id} below can race the in-flight POST and land on // an empty listing. await expect(page.getByTestId('move-dialog')).toHaveCount(0, { timeout: 15_000 }); // Copy leaves the source in place. await expect(page.getByTestId(srcName)).toBeVisible({ timeout: 15_000 }); // And a copy now lives in the destination. await page.goto(`/files/${dest.id}`); await expect(page.getByTestId(srcName)).toBeVisible({ timeout: 15_000 }); });