diff --git a/cypress/e2e/groupfoldersNavigation.cy.ts b/cypress/e2e/groupfoldersNavigation.cy.ts new file mode 100644 index 000000000..ea47043ec --- /dev/null +++ b/cypress/e2e/groupfoldersNavigation.cy.ts @@ -0,0 +1,104 @@ +/*! + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { User } from '@nextcloud/e2e-test-server/cypress' + +import { + PERMISSION_READ, + PERMISSION_WRITE, + addUserToGroup, + createGroup, + createGroupFolder, + deleteGroupFolder, + fileOrFolderExists, +} from './groupfoldersUtils.ts' +import { randHash } from '../utils/index.js' + +// Regression coverage for https://github.com/nextcloud/groupfolders/issues/4499: +// clicking a team folder from the Team folders view must navigate to the +// target route *with* the fileid segment (PR #4524) and render content — including +// on repeated navigation, which is where the stale-route / abort-race failures +// used to surface. +describe('Team folders view navigation', () => { + let user: User + let groupFolderId: string + let groupName: string + let groupFolderName: string + + beforeEach(() => { + if (groupFolderId) { + deleteGroupFolder(groupFolderId) + } + groupName = `test_group_${randHash()}` + groupFolderName = `test_group_folder_${randHash()}` + + cy.createRandomUser().then(_user => { + user = _user + createGroup(groupName).then(() => { + addUserToGroup(groupName, user.userId) + createGroupFolder(groupFolderName, groupName, [PERMISSION_READ, PERMISSION_WRITE]) + .then(_id => { + groupFolderId = _id + }) + }) + }) + }) + + it('clicking a team folder navigates to /apps/files/files/ with dir query and renders content', () => { + cy.uploadContent(user, new Blob(['hello']), 'text/plain', `/${groupFolderName}/file1.txt`) + + cy.login(user) + cy.visit('/apps/files/groupfolders') + cy.location('pathname').should('include', '/apps/files/groupfolders') + + // The Team folders view is served by the groupfolders DAV endpoint and keys + // rows by group-folder id rather than mount-point name — locate by fileid. + cy.get('[data-cy-files-list-row-fileid]').should('have.length.at.least', 1) + + cy.intercept({ method: 'PROPFIND', url: `**/dav/files/**/${groupFolderName}` }).as('propFindFolder') + cy.get('[data-cy-files-list-row-fileid]').first() + .find('[data-cy-files-list-row-name-link]') + .click({ force: true }) + cy.wait('@propFindFolder') + + // PR 4524: route must include the fileid segment, not just /apps/files/files?dir=... + cy.location('pathname').should('match', /\/apps\/files\/files\/\d+$/) + cy.location('search').should('contain', `dir=/${groupFolderName}`) + + // If exec returns before navigation settles or lets a router rejection bubble, + // the file list stays empty / shows "folder not found". + fileOrFolderExists('file1.txt') + }) + + it('navigating back to the Team folders view and re-entering the same folder still works', () => { + cy.uploadContent(user, new Blob(['hello']), 'text/plain', `/${groupFolderName}/file1.txt`) + + cy.login(user) + cy.visit('/apps/files/groupfolders') + cy.location('pathname').should('include', '/apps/files/groupfolders') + cy.get('[data-cy-files-list-row-fileid]').should('have.length.at.least', 1) + + cy.intercept({ method: 'PROPFIND', url: `**/dav/files/**/${groupFolderName}` }).as('propFind1') + cy.get('[data-cy-files-list-row-fileid]').first() + .find('[data-cy-files-list-row-name-link]') + .click({ force: true }) + cy.wait('@propFind1') + fileOrFolderExists('file1.txt') + + cy.visit('/apps/files/groupfolders') + cy.location('pathname').should('include', '/apps/files/groupfolders') + cy.get('[data-cy-files-list-row-fileid]').should('have.length.at.least', 1) + + // Second click — the stale-router path from the bug report. + cy.intercept({ method: 'PROPFIND', url: `**/dav/files/**/${groupFolderName}` }).as('propFind2') + cy.get('[data-cy-files-list-row-fileid]').first() + .find('[data-cy-files-list-row-name-link]') + .click({ force: true }) + cy.wait('@propFind2') + + cy.location('pathname').should('match', /\/apps\/files\/files\/\d+$/) + fileOrFolderExists('file1.txt') + }) +}) diff --git a/src/actions/openGroupfolderAction.ts b/src/actions/openGroupfolderAction.ts index c3ed92617..1a4443d69 100644 --- a/src/actions/openGroupfolderAction.ts +++ b/src/actions/openGroupfolderAction.ts @@ -16,13 +16,12 @@ export const action: IFileAction = { enabled: ({ view }) => view.id === appName, async exec({ nodes }) { - const dir = nodes[0].attributes.mountPoint - window.OCP.Files.Router.goToRoute( + await window.OCP.Files.Router.goToRoute( null, // use default route { view: 'files', fileid: nodes[0].id }, - { dir }, + { dir: nodes[0].attributes.mountPoint }, ) - return null + return true }, default: DefaultType.DEFAULT,