Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 0 additions & 69 deletions cypress/e2e/files_trashbin/files-trash-action.cy.ts

This file was deleted.

137 changes: 0 additions & 137 deletions cypress/e2e/files_trashbin/files.cy.ts

This file was deleted.

56 changes: 56 additions & 0 deletions tests/playwright/e2e/files_trashbin/files-trash-action.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*!
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { expect, test } from '../../support/fixtures/files-page.ts'
import { rm, uploadContent } from '../../support/utils/dav.ts'

const FILE_COUNT = 5

test.describe('files_trashbin: empty trashbin action', () => {
test.beforeEach(async ({ page, user }) => {
// Create FILE_COUNT files and move them all to the trash
for (let index = 0; index < FILE_COUNT; index++) {
await uploadContent(page.request, user, '<content>', 'text/plain', `/file${index}.txt`)
await rm(page.request, user, `/file${index}.txt`)
}
})

test('can empty trashbin', async ({ page, filesListPage }) => {
await filesListPage.open()
// Home holds only the default welcome file and offers no empty-trash action
await expect(filesListPage.getRows()).toHaveCount(1)
await expect(filesListPage.getListActionButton('empty-trash')).toHaveCount(0)

await filesListPage.open('trashbin')
await expect(filesListPage.getRows()).toHaveCount(FILE_COUNT)

const emptied = page.waitForResponse(
(r) => r.request().method() === 'DELETE' && r.url().includes('/remote.php/dav/trashbin/'),
)
await filesListPage.triggerListAction('empty-trash')

// Confirm in the dialog
await page.getByRole('dialog')
.getByRole('button', { name: 'Empty deleted files' })
.click()

expect((await emptied).status()).toBe(204)
await expect(filesListPage.getRows()).toHaveCount(0)
})

test('cancelling the empty trashbin action does not delete anything', async ({ page, filesListPage }) => {
await filesListPage.open('trashbin')
await expect(filesListPage.getRows()).toHaveCount(FILE_COUNT)

await filesListPage.triggerListAction('empty-trash')

// Cancel the dialog: no request is sent and the files remain
await page.getByRole('dialog')
.getByRole('button', { name: 'Cancel' })
.click()

await expect(filesListPage.getRows()).toHaveCount(FILE_COUNT)
})
})
102 changes: 102 additions & 0 deletions tests/playwright/e2e/files_trashbin/files.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*!
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { Locator, Page } from '@playwright/test'
import { readFile } from 'node:fs/promises'
import { expect, test } from '../../support/fixtures/files-trashbin-page.ts'
import { mkdir, rm, uploadContent } from '../../support/utils/dav.ts'
import { ALL_PERMISSIONS, ShareType, createShare } from '../../support/utils/sharing.ts'
import { setUserDisplayName } from '../../support/utils/users.ts'

// Trashbin custom columns, keyed by their product-owned column ids
const ORIGINAL_LOCATION = '[data-cy-files-list-row-column-custom="files_trashbin--original-location"]'
const DELETED_BY = '[data-cy-files-list-row-column-custom="files_trashbin--deleted-by"]'
const DELETED_AT = '[data-cy-files-list-row-column-custom="files_trashbin--deleted"]'

/** Run `trigger`, then assert it downloaded `file.txt` with the expected content. */
async function expectFileDownload(page: Page, trigger: () => Promise<void>) {
const downloadPromise = page.waitForEvent('download')
await trigger()
const download = await downloadPromise
expect(download.suggestedFilename()).toBe('file.txt')
expect(await readFile(await download.path(), 'utf-8')).toBe('<content>')
}

/** Assert a trashbin row's name and custom columns (the deleted time is always recent). */
async function expectTrashbinRow(row: Locator, name: string, location: string, deletedBy: string) {
await expect(row).toBeVisible()
// Name and extension render as separate spans, so the composed text has a space
await expect(row.locator('[data-cy-files-list-row-name]')).toHaveText(name)
await expect(row.locator(ORIGINAL_LOCATION)).toHaveText(location)
await expect(row.locator(DELETED_BY)).toHaveText(deletedBy)
await expect(row.locator(DELETED_AT)).toHaveText('few seconds ago')
}

test.describe('files_trashbin: download files', () => {
let fileIds: [number, number]

test.beforeEach(async ({ page, user, filesListPage }) => {
const first = await uploadContent(page.request, user, '<content>', 'text/plain', '/file.txt')
await rm(page.request, user, '/file.txt')
const second = await uploadContent(page.request, user, '<content>', 'text/plain', '/other-file.txt')
await rm(page.request, user, '/other-file.txt')
fileIds = [Number(first), Number(second)]

await filesListPage.open('trashbin')
})

test('can download a file', async ({ page, filesListPage }) => {
await expect(filesListPage.getRowForFileId(fileIds[0])).toBeVisible()
await expect(filesListPage.getRowForFileId(fileIds[1])).toBeVisible()

await expectFileDownload(page, () => filesListPage.triggerActionForFileId(fileIds[0], 'download'))
})

test('can download a file using the default action', async ({ page, filesListPage }) => {
await expectFileDownload(page, () =>
// The inline "Download" button is the row's default action; force past the sticky header
filesListPage.getRowForFileId(fileIds[0])
.getByRole('button', { name: 'Download' })
.click({ force: true }),
)
})

// Trashbin has no bulk download: the webdav zip-folder plugin does not work for
// the trashbin (and never did with the legacy ajax download either).
test('does not offer bulk download', async ({ page, filesListPage }) => {
await expect(filesListPage.getRowCheckboxes()).toHaveCount(2)
await filesListPage.selectAll()
await expect(page.getByText('2 selected')).toBeVisible()

await expect(filesListPage.getSelectionActionEntry('restore')).toBeVisible()
await expect(filesListPage.getSelectionActionEntry('download')).toHaveCount(0)
})
})

test.describe('files_trashbin: file row', () => {
test('shows data for a file deleted by the owner', async ({ user, aliceRequest, filesListPage }) => {
const fileId = Number(await uploadContent(aliceRequest, user, '<content>', 'text/plain', '/test-file.txt'))
await rm(aliceRequest, user, '/test-file.txt')

await filesListPage.open('trashbin')

// The owner's own deletions render as "You" regardless of display name
await expectTrashbinRow(filesListPage.getRowForFileId(fileId), 'test-file .txt', 'All files', 'You')
})

test('shows data for a file deleted by a sharee in a group share', async ({ user, aliceRequest, bob, bobRequest, group, filesListPage }) => {
await setUserDisplayName(bobRequest, bob.userId, 'Bob')
await mkdir(aliceRequest, user, '/Shared')
await createShare(aliceRequest, '/Shared', group, ALL_PERMISSIONS, ShareType.GROUP)

const fileId = Number(await uploadContent(aliceRequest, user, '<content>', 'text/plain', '/Shared/test-file.txt'))
// Bob (the sharee) deletes the file from his view of the shared folder
await rm(bobRequest, bob, '/Shared/test-file.txt')

await filesListPage.open('trashbin')

await expectTrashbinRow(filesListPage.getRowForFileId(fileId), 'test-file .txt', 'Shared', 'Bob')
})
})
Loading
Loading