Merge pull request #50364 from nextcloud/fix/files-header-submenu

This commit is contained in:
John Molakvoæ 2025-02-07 10:20:29 +01:00 committed by GitHub
commit f21ffabe0e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 453 additions and 79 deletions

View file

@ -294,8 +294,9 @@ async function openFilePickerForAction(
return promise
}
export const ACTION_COPY_MOVE = 'move-copy'
export const action = new FileAction({
id: 'move-copy',
id: ACTION_COPY_MOVE,
displayName(nodes: Node[]) {
switch (getActionForNodes(nodes)) {
case MoveCopyAction.MOVE:

View file

@ -30,11 +30,11 @@
:ref="`action-${action.id}`"
:class="{
[`files-list__row-action-${action.id}`]: true,
[`files-list__row-action--menu`]: isMenu(action.id)
[`files-list__row-action--menu`]: isValidMenu(action)
}"
:close-after-click="!isMenu(action.id)"
:close-after-click="!isValidMenu(action)"
:data-cy-files-list-row-action="action.id"
:is-menu="isMenu(action.id)"
:is-menu="isValidMenu(action)"
:aria-label="action.title?.([source], currentView)"
:title="action.title?.([source], currentView)"
@click="onActionClick(action)">
@ -48,7 +48,7 @@
<!-- Submenu actions list-->
<template v-if="openedSubmenu && enabledSubmenuActions[openedSubmenu?.id]">
<!-- Back to top-level button -->
<NcActionButton class="files-list__row-action-back" @click="onBackToMenuClick(openedSubmenu)">
<NcActionButton class="files-list__row-action-back" data-cy-files-list-row-action="menu-back" @click="onBackToMenuClick(openedSubmenu)">
<template #icon>
<ArrowLeftIcon />
</template>
@ -83,8 +83,8 @@ import type { FileAction, Node } from '@nextcloud/files'
import { DefaultType, NodeStatus } from '@nextcloud/files'
import { defineComponent, inject } from 'vue'
import { translate as t } from '@nextcloud/l10n'
import { useHotKey } from '@nextcloud/vue/dist/Composables/useHotKey.js'
import ArrowLeftIcon from 'vue-material-design-icons/ArrowLeft.vue'
import CustomElementRender from '../CustomElementRender.vue'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
@ -98,6 +98,7 @@ import { useActiveStore } from '../../store/active.ts'
import { useFileListWidth } from '../../composables/useFileListWidth.ts'
import { useNavigation } from '../../composables/useNavigation'
import { useRouteParameters } from '../../composables/useRouteParameters.ts'
import actionsMixins from '../../mixins/actionsMixin.ts'
import logger from '../../logger.ts'
export default defineComponent({
@ -113,6 +114,8 @@ export default defineComponent({
NcLoadingIcon,
},
mixins: [actionsMixins],
props: {
opened: {
type: Boolean,
@ -146,12 +149,6 @@ export default defineComponent({
}
},
data() {
return {
openedSubmenu: null as FileAction | null,
}
},
computed: {
isActive() {
return this.activeStore?.activeNode?.source === this.source.source
@ -209,18 +206,6 @@ export default defineComponent({
return actions.filter(action => !(action.parent && topActionsIds.includes(action.parent)))
},
enabledSubmenuActions() {
return this.enabledFileActions
.filter(action => action.parent)
.reduce((arr, action) => {
if (!arr[action.parent!]) {
arr[action.parent!] = []
}
arr[action.parent!].push(action)
return arr
}, {} as Record<string, FileAction[]>)
},
openedMenu: {
get() {
return this.opened
@ -287,7 +272,7 @@ export default defineComponent({
return this.activeStore?.activeAction?.id === action.id
},
async onActionClick(action, isSubmenu = false) {
async onActionClick(action) {
// If the action is a submenu, we open it
if (this.enabledSubmenuActions[action.id]) {
this.openedSubmenu = action
@ -299,30 +284,6 @@ export default defineComponent({
// Execute the action
await executeAction(action)
// If that was a submenu, we just go back after the action
if (isSubmenu) {
this.openedSubmenu = null
}
},
isMenu(id: string) {
return this.enabledSubmenuActions[id]?.length > 0
},
async onBackToMenuClick(action: FileAction) {
this.openedSubmenu = null
// Wait for first render
await this.$nextTick()
// Focus the previous menu action button
this.$nextTick(() => {
// Focus the action button
const menuAction = this.$refs[`action-${action.id}`]?.[0]
if (menuAction) {
menuAction.$el.querySelector('button')?.focus()
}
})
},
onKeyDown(event: KeyboardEvent) {

View file

@ -8,14 +8,23 @@
container="#app-content-vue"
:disabled="!!loading || areSomeNodesLoading"
:force-name="true"
:inline="inlineActions"
:menu-name="inlineActions <= 1 ? t('files', 'Actions') : null"
:open.sync="openedMenu">
<NcActionButton v-for="action in enabledActions"
:inline="enabledInlineActions.length"
:menu-name="enabledInlineActions.length <= 1 ? t('files', 'Actions') : null"
:open.sync="openedMenu"
@close="openedSubmenu = null">
<!-- Default actions list-->
<NcActionButton v-for="action in enabledMenuActions"
:key="action.id"
:aria-label="action.displayName(nodes, currentView) + ' ' + t('files', '(selected)') /** TRANSLATORS: Selected like 'selected files and folders' */"
:class="'files-list__row-actions-batch-' + action.id"
:ref="`action-batch-${action.id}`"
:class="{
[`files-list__row-actions-batch-${action.id}`]: true,
[`files-list__row-actions-batch--menu`]: isValidMenu(action)
}"
:close-after-click="!isValidMenu(action)"
:data-cy-files-list-selection-action="action.id"
:is-menu="isValidMenu(action)"
:aria-label="action.displayName(nodes, currentView) + ' ' + t('files', '(selected)') /** TRANSLATORS: Selected like 'selected files and folders' */"
:title="action.title?.(nodes, currentView)"
@click="onActionClick(action)">
<template #icon>
<NcLoadingIcon v-if="loading === action.id" :size="18" />
@ -23,20 +32,50 @@
</template>
{{ action.displayName(nodes, currentView) }}
</NcActionButton>
<!-- Submenu actions list-->
<template v-if="openedSubmenu && enabledSubmenuActions[openedSubmenu?.id]">
<!-- Back to top-level button -->
<NcActionButton class="files-list__row-actions-batch-back" data-cy-files-list-selection-action="menu-back" @click="onBackToMenuClick(openedSubmenu)">
<template #icon>
<ArrowLeftIcon />
</template>
{{ t('files', 'Back') }}
</NcActionButton>
<NcActionSeparator />
<!-- Submenu actions -->
<NcActionButton v-for="action in enabledSubmenuActions[openedSubmenu?.id]"
:key="action.id"
:class="`files-list__row-actions-batch-${action.id}`"
class="files-list__row-actions-batch--submenu"
close-after-click
:data-cy-files-list-selection-action="action.id"
:aria-label="action.displayName(nodes, currentView) + ' ' + t('files', '(selected)') /** TRANSLATORS: Selected like 'selected files and folders' */"
:title="action.title?.(nodes, currentView)"
@click="onActionClick(action)">
<template #icon>
<NcLoadingIcon v-if="loading === action.id" :size="18" />
<NcIconSvgWrapper v-else :svg="action.iconSvgInline(nodes, currentView)" />
</template>
{{ action.displayName(nodes, currentView) }}
</NcActionButton>
</template>
</NcActions>
</div>
</template>
<script lang="ts">
import type { Node, View } from '@nextcloud/files'
import type { FileAction, Node, View } from '@nextcloud/files'
import type { PropType } from 'vue'
import type { FileSource } from '../types'
import { NodeStatus, getFileActions } from '@nextcloud/files'
import { getFileActions, NodeStatus, DefaultType } from '@nextcloud/files'
import { showError, showSuccess } from '@nextcloud/dialogs'
import { translate } from '@nextcloud/l10n'
import { defineComponent } from 'vue'
import ArrowLeftIcon from 'vue-material-design-icons/ArrowLeft.vue'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
@ -47,6 +86,7 @@ import { useFileListWidth } from '../composables/useFileListWidth.ts'
import { useActionsMenuStore } from '../store/actionsmenu.ts'
import { useFilesStore } from '../store/files.ts'
import { useSelectionStore } from '../store/selection.ts'
import actionsMixins from '../mixins/actionsMixin.ts'
import logger from '../logger.ts'
// The registered actions list
@ -56,12 +96,15 @@ export default defineComponent({
name: 'FilesListTableHeaderActions',
components: {
ArrowLeftIcon,
NcActions,
NcActionButton,
NcIconSvgWrapper,
NcLoadingIcon,
},
mixins: [actionsMixins],
props: {
currentView: {
type: Object as PropType<View>,
@ -97,13 +140,78 @@ export default defineComponent({
},
computed: {
enabledActions() {
enabledFileActions(): FileAction[] {
return actions
.filter(action => action.execBatch)
// We don't handle renderInline actions in this component
.filter(action => !action.renderInline)
// We don't handle actions that are not visible
.filter(action => action.default !== DefaultType.HIDDEN)
.filter(action => !action.enabled || action.enabled(this.nodes, this.currentView))
.sort((a, b) => (a.order || 0) - (b.order || 0))
},
/**
* Return the list of enabled actions that are
* allowed to be rendered inlined.
* This means that they are not within a menu, nor
* being the parent of submenu actions.
*/
enabledInlineActions(): FileAction[] {
return this.enabledFileActions
// Remove all actions that are not top-level actions
.filter(action => action.parent === undefined)
// Remove all actions that are not batch actions
.filter(action => action.execBatch !== undefined)
// Remove all top-menu entries
.filter(action => !this.isValidMenu(action))
// Return a maximum actions to fit the screen
.slice(0, this.inlineActions)
},
/**
* Return the rest of enabled actions that are not
* rendered inlined.
*/
enabledMenuActions(): FileAction[] {
// If we're in a submenu, only render the inline
// actions before the filtered submenu
if (this.openedSubmenu) {
return this.enabledInlineActions
}
// We filter duplicates to prevent inline actions to be shown twice
const actions = this.enabledFileActions.filter((value, index, self) => {
return index === self.findIndex(action => action.id === value.id)
})
// Generate list of all top-level actions ids
const childrenActionsIds = actions.filter(action => action.parent).map(action => action.parent) as string[]
const menuActions = actions
.filter(action => {
// If the action is not a batch action, we need
// to make sure it's a top-level parent entry
// and that we have some children actions bound to it
if (!action.execBatch) {
return childrenActionsIds.includes(action.id)
}
// Rendering second-level actions is done in the template
// when openedSubmenu is set.
if (action.parent) {
return false
}
return true
})
.filter(action => !this.enabledInlineActions.includes(action))
// Make sure we render the inline actions first
// and then the rest of the actions.
// We do NOT want nested actions to be rendered inlined
return [...this.enabledInlineActions, ...menuActions]
},
nodes() {
return this.selectedNodes
.map(source => this.getNode(source))
@ -148,6 +256,12 @@ export default defineComponent({
},
async onActionClick(action) {
// If the action is a submenu, we open it
if (this.enabledSubmenuActions[action.id]) {
this.openedSubmenu = action
return
}
let displayName = action.id
try {
displayName = action.displayName(this.nodes, this.currentView)

View file

@ -0,0 +1,65 @@
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { FileAction } from '@nextcloud/files'
import { defineComponent } from 'vue'
export default defineComponent({
data() {
return {
openedSubmenu: null as FileAction|null,
}
},
computed: {
enabledSubmenuActions(): Record<string, FileAction[]> {
return (this.enabledFileActions as FileAction[])
.reduce((record, action) => {
if (action.parent !== undefined) {
if (!record[action.parent]) {
record[action.parent] = []
}
record[action.parent].push(action)
}
return record
}, {} as Record<string, FileAction[]>)
},
},
methods: {
/**
* Check if a menu is valid, meaning it is
* defined and has at least one action
*
* @param action The action to check
*/
isValidMenu(action: FileAction): boolean {
return this.enabledSubmenuActions[action.id]?.length > 0
},
async onBackToMenuClick(action: FileAction|null) {
if (!action) {
return
}
this.openedSubmenu = null
// Wait for first render
await this.$nextTick()
// Focus the previous menu action button
this.$nextTick(() => {
// Focus the action button, test both batch and single action references
// as this mixin is used in both single and batch actions.
const menuAction = this.$refs[`action-batch-${action.id}`]?.[0]
|| this.$refs[`action-${action.id}`]?.[0]
if (menuAction) {
menuAction.$el.querySelector('button')?.focus()
}
})
},
},
})

View file

@ -40,7 +40,7 @@ export const useFiltersStore = defineStore('filters', () => {
* All filters that provide a UI for visual controlling the filter state
*/
const filtersWithUI = computed<Required<IFileListFilter>[]>(
() => sortedFilters.value.filter(isFileListFilterWithUi)
() => sortedFilters.value.filter(isFileListFilterWithUi),
)
/**

View file

@ -22,8 +22,9 @@ const isExternal = (node: Node) => {
return node.attributes?.['is-federated'] ?? false
}
export const ACTION_SHARING_STATUS = 'sharing-status'
export const action = new FileAction({
id: 'sharing-status',
id: ACTION_SHARING_STATUS,
displayName(nodes: Node[]) {
const node = nodes[0]
const shareTypes = Object.values(node?.attributes?.['share-types'] || {}).flat() as number[]

View file

@ -4,6 +4,7 @@
*/
import type { User } from '@nextcloud/cypress'
import { ACTION_COPY_MOVE } from "../../../apps/files/src/actions/moveOrCopyAction"
export const getRowForFileId = (fileid: number) => cy.get(`[data-cy-files-list-row-fileid="${fileid}"]`)
export const getRowForFile = (filename: string) => cy.get(`[data-cy-files-list-row-name="${CSS.escape(filename)}"]`)
@ -14,16 +15,25 @@ export const getActionsForFile = (filename: string) => getRowForFile(filename).f
export const getActionButtonForFileId = (fileid: number) => getActionsForFileId(fileid).findByRole('button', { name: 'Actions' })
export const getActionButtonForFile = (filename: string) => getActionsForFile(filename).findByRole('button', { name: 'Actions' })
export const getActionEntryForFileId = (fileid: number, actionId: string) => {
return cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
}
export const getActionEntryForFile = (filename: string, actionId: string) => {
return cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`)
}
export const triggerActionForFileId = (fileid: number, actionId: string) => {
// Even if it's inline, we open the action menu to get all actions visible
getActionButtonForFileId(fileid).click({ force: true })
// Getting the last button to avoid the one from popup fading out
cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).last()
getActionEntryForFileId(fileid, actionId)
.find('button').last()
.should('exist').click({ force: true })
}
export const triggerActionForFile = (filename: string, actionId: string) => {
// Even if it's inline, we open the action menu to get all actions visible
getActionButtonForFile(filename).click({ force: true })
// Getting the last button to avoid the one from popup fading out
cy.get(`[data-cy-files-list-row-action="${CSS.escape(actionId)}"] > button`).last()
getActionEntryForFile(filename, actionId)
.find('button').last()
.should('exist').click({ force: true })
}
@ -31,7 +41,7 @@ export const triggerInlineActionForFileId = (fileid: number, actionId: string) =
getActionsForFileId(fileid).find(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`).should('exist').click()
}
export const triggerInlineActionForFile = (filename: string, actionId: string) => {
getActionsForFile(filename).get(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`).should('exist').click()
getActionsForFile(filename).find(`button[data-cy-files-list-row-action="${CSS.escape(actionId)}"]`).should('exist').click()
}
export const selectAllFiles = () => {
@ -58,13 +68,21 @@ export const selectRowForFile = (filename: string, options: Partial<Cypress.Clic
}
export const getSelectionActionButton = () => cy.get('[data-cy-files-list-selection-actions]').findByRole('button', { name: 'Actions' })
export const getSelectionActionEntry = (actionId: string) => cy.get(`[data-cy-files-list-selection-action="${CSS.escape(actionId)}"]`)
export const triggerSelectionAction = (actionId: string) => {
cy.get(`button[data-cy-files-list-selection-action="${CSS.escape(actionId)}"]`).should('exist').click()
// Even if it's inline, we open the action menu to get all actions visible
getSelectionActionButton().click({ force: true })
// the entry might already be a button or a button might its child
getSelectionActionEntry(actionId)
.then($el => $el.is('button') ? cy.wrap($el) : cy.wrap($el).findByRole('button').last())
.should('exist')
.click()
}
export const moveFile = (fileName: string, dirPath: string) => {
getRowForFile(fileName).should('be.visible')
triggerActionForFile(fileName, 'move-copy')
triggerActionForFile(fileName, ACTION_COPY_MOVE)
cy.get('.file-picker').within(() => {
// intercept the copy so we can wait for it
@ -95,7 +113,7 @@ export const moveFile = (fileName: string, dirPath: string) => {
export const copyFile = (fileName: string, dirPath: string) => {
getRowForFile(fileName).should('be.visible')
triggerActionForFile(fileName, 'move-copy')
triggerActionForFile(fileName, ACTION_COPY_MOVE)
cy.get('.file-picker').within(() => {
// intercept the copy so we can wait for it

View file

@ -0,0 +1,214 @@
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { User } from '@nextcloud/cypress'
import { FileAction } from '@nextcloud/files'
import { getActionButtonForFileId, getActionEntryForFileId, getRowForFile, getSelectionActionButton, getSelectionActionEntry, selectRowForFile, triggerActionForFile, triggerActionForFileId } from './FilesUtils'
import { ACTION_COPY_MOVE } from '../../../apps/files/src/actions/moveOrCopyAction'
import { ACTION_DELETE } from '../../../apps/files/src/actions/deleteAction'
import { ACTION_DETAILS } from '../../../apps/files/src/actions/sidebarAction'
import { ACTION_SHARING_STATUS } from '../../../apps/files_sharing/src/files_actions/sharingStatusAction'
declare global {
interface Window {
_nc_fileactions: FileAction[]
}
}
// Those two arrays doesn't represent the full list of actions
// the goal is to test a few, we're not trying to match the full feature set
const expectedDefaultActionsIDs = [
ACTION_COPY_MOVE,
ACTION_DELETE,
ACTION_DETAILS,
ACTION_SHARING_STATUS,
]
const expectedDefaultSelectionActionsIDs = [
ACTION_COPY_MOVE,
ACTION_DELETE,
]
describe('Files: Actions', { testIsolation: true }, () => {
let user: User
let fileId: number = 0
beforeEach(() => cy.createRandomUser().then(($user) => {
user = $user
cy.uploadContent(user, new Blob([]), 'image/jpeg', '/image.jpg').then((response) => {
fileId = Number.parseInt(response.headers['oc-fileid'] ?? '0')
})
cy.login(user)
}))
it('Show some standard actions', () => {
cy.visit('/apps/files')
getRowForFile('image.jpg').should('be.visible')
expectedDefaultActionsIDs.forEach((actionId) => {
// Open the menu
getActionButtonForFileId(fileId).click({ force: true })
// Check the action is visible
getActionEntryForFileId(fileId, actionId).should('be.visible')
})
})
it('Show some nested actions', () => {
const parent = new FileAction({
id: 'nested-action',
displayName: () => 'Nested Action',
exec: cy.spy(),
iconSvgInline: () => '<svg></svg>',
})
const child1 = new FileAction({
id: 'nested-child-1',
displayName: () => 'Nested Child 1',
exec: cy.spy(),
iconSvgInline: () => '<svg></svg>',
parent: 'nested-action',
})
const child2 = new FileAction({
id: 'nested-child-2',
displayName: () => 'Nested Child 2',
exec: cy.spy(),
iconSvgInline: () => '<svg></svg>',
parent: 'nested-action',
})
cy.visit('/apps/files', {
// Cannot use registerFileAction here
onBeforeLoad: (win) => {
if (!win._nc_fileactions) win._nc_fileactions = []
// Cannot use registerFileAction here
win._nc_fileactions.push(parent)
win._nc_fileactions.push(child1)
win._nc_fileactions.push(child2)
}
})
// Open the menu
getActionButtonForFileId(fileId).click({ force: true })
// Check we have the parent action but not the children
getActionEntryForFileId(fileId, 'nested-action').should('be.visible')
getActionEntryForFileId(fileId, 'menu-back').should('not.exist')
getActionEntryForFileId(fileId, 'nested-child-1').should('not.exist')
getActionEntryForFileId(fileId, 'nested-child-2').should('not.exist')
// Click on the parent action
getActionEntryForFileId(fileId, 'nested-action')
.find('button').last()
.should('exist').click({ force: true })
// Check we have the children and the back button but not the parent
getActionEntryForFileId(fileId, 'nested-action').should('not.exist')
getActionEntryForFileId(fileId, 'menu-back').should('be.visible')
getActionEntryForFileId(fileId, 'nested-child-1').should('be.visible')
getActionEntryForFileId(fileId, 'nested-child-2').should('be.visible')
// Click on the back button
getActionEntryForFileId(fileId, 'menu-back')
.find('button').last()
.should('exist').click({ force: true })
// Check we have the parent action but not the children
getActionEntryForFileId(fileId, 'nested-action').should('be.visible')
getActionEntryForFileId(fileId, 'menu-back').should('not.exist')
getActionEntryForFileId(fileId, 'nested-child-1').should('not.exist')
getActionEntryForFileId(fileId, 'nested-child-2').should('not.exist')
})
it('Show some actions for a selection', () => {
cy.visit('/apps/files')
getRowForFile('image.jpg').should('be.visible')
selectRowForFile('image.jpg')
cy.get('[data-cy-files-list-selection-actions]').should('be.visible')
getSelectionActionButton().should('be.visible')
// Open the menu
getSelectionActionButton().click({ force: true })
// Check the action is visible
expectedDefaultSelectionActionsIDs.forEach((actionId) => {
getSelectionActionEntry(actionId).should('be.visible')
})
})
it('Show some nested actions for a selection', () => {
const parent = new FileAction({
id: 'nested-action',
displayName: () => 'Nested Action',
exec: cy.spy(),
iconSvgInline: () => '<svg></svg>',
})
const child1 = new FileAction({
id: 'nested-child-1',
displayName: () => 'Nested Child 1',
exec: cy.spy(),
execBatch: cy.spy(),
iconSvgInline: () => '<svg></svg>',
parent: 'nested-action',
})
const child2 = new FileAction({
id: 'nested-child-2',
displayName: () => 'Nested Child 2',
exec: cy.spy(),
execBatch: cy.spy(),
iconSvgInline: () => '<svg></svg>',
parent: 'nested-action',
})
cy.visit('/apps/files', {
// Cannot use registerFileAction here
onBeforeLoad: (win) => {
if (!win._nc_fileactions) win._nc_fileactions = []
// Cannot use registerFileAction here
win._nc_fileactions.push(parent)
win._nc_fileactions.push(child1)
win._nc_fileactions.push(child2)
}
})
selectRowForFile('image.jpg')
// Open the menu
getSelectionActionButton().click({ force: true })
// Check we have the parent action but not the children
getSelectionActionEntry('nested-action').should('be.visible')
getSelectionActionEntry('menu-back').should('not.exist')
getSelectionActionEntry('nested-child-1').should('not.exist')
getSelectionActionEntry('nested-child-2').should('not.exist')
// Click on the parent action
getSelectionActionEntry('nested-action')
.find('button').last()
.should('exist').click({ force: true })
// Check we have the children and the back button but not the parent
getSelectionActionEntry('nested-action').should('not.exist')
getSelectionActionEntry('menu-back').should('be.visible')
getSelectionActionEntry('nested-child-1').should('be.visible')
getSelectionActionEntry('nested-child-2').should('be.visible')
// Click on the back button
getSelectionActionEntry('menu-back')
.find('button').last()
.should('exist').click({ force: true })
// Check we have the parent action but not the children
getSelectionActionEntry('nested-action').should('be.visible')
getSelectionActionEntry('menu-back').should('not.exist')
getSelectionActionEntry('nested-child-1').should('not.exist')
getSelectionActionEntry('nested-child-2').should('not.exist')
})
})

View file

@ -35,7 +35,7 @@ export function createShare(fileName: string, username: string, shareSettings: P
export function openSharingDetails(index: number) {
cy.get('#app-sidebar-vue').within(() => {
cy.get('[data-cy-files-sharing-share-actions]').eq(index).click()
cy.get('[data-cy-files-sharing-share-actions]').eq(index).click({ force: true })
cy.get('[data-cy-files-sharing-share-permissions-bundle="custom"]').click()
})
}

View file

@ -10,10 +10,11 @@ import {
navigateToFolder,
triggerActionForFile,
} from '../files/FilesUtils.ts'
import { ACTION_COPY_MOVE } from '../../../apps/files/src/actions/moveOrCopyAction.ts'
export const copyFileForbidden = (fileName: string, dirPath: string) => {
getRowForFile(fileName).should('be.visible')
triggerActionForFile(fileName, 'move-copy')
triggerActionForFile(fileName, ACTION_COPY_MOVE)
cy.get('.file-picker').within(() => {
// intercept the copy so we can wait for it
@ -32,7 +33,7 @@ export const copyFileForbidden = (fileName: string, dirPath: string) => {
export const moveFileForbidden = (fileName: string, dirPath: string) => {
getRowForFile(fileName).should('be.visible')
triggerActionForFile(fileName, 'move-copy')
triggerActionForFile(fileName, ACTION_COPY_MOVE)
cy.get('.file-picker').within(() => {
// intercept the copy so we can wait for it

View file

@ -59,7 +59,7 @@ describe('files_sharing: Note to recipient', { testIsolation: true }, () => {
cy.get('[data-cy-sidebar]').within(() => {
// Open the share
cy.get('[data-cy-files-sharing-share-actions]').first().click()
cy.get('[data-cy-files-sharing-share-actions]').first().click({ force: true })
// Open the custom settings
cy.get('[data-cy-files-sharing-share-permissions-bundle="custom"]').click()

View file

@ -57,7 +57,6 @@ function triggerTagManagementDialogAction() {
}
describe('Systemtags: Files bulk action', { testIsolation: false }, () => {
let snapshot: string
let user1: User
let user2: User

File diff suppressed because one or more lines are too long

4
dist/files-main.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long