mirror of
https://github.com/nextcloud/server.git
synced 2026-06-07 15:53:04 -04:00
Merge pull request #46939 from nextcloud/backport/46768/stable28
[stable28] fix(files): Provide default file action for file entry name (on click action)
This commit is contained in:
commit
e5d7550a5e
43 changed files with 964 additions and 555 deletions
|
|
@ -21,7 +21,7 @@
|
|||
*/
|
||||
import { action } from './downloadAction'
|
||||
import { expect } from '@jest/globals'
|
||||
import { File, Folder, Permission, View, FileAction } from '@nextcloud/files'
|
||||
import { File, Folder, Permission, View, FileAction, DefaultType } from '@nextcloud/files'
|
||||
|
||||
const view = {
|
||||
id: 'files',
|
||||
|
|
@ -34,7 +34,7 @@ describe('Download action conditions tests', () => {
|
|||
expect(action.id).toBe('download')
|
||||
expect(action.displayName([], view)).toBe('Download')
|
||||
expect(action.iconSvgInline([], view)).toBe('<svg>SvgMock</svg>')
|
||||
expect(action.default).toBeUndefined()
|
||||
expect(action.default).toBe(DefaultType.DEFAULT)
|
||||
expect(action.order).toBe(30)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
*
|
||||
*/
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { FileAction, Permission, Node, FileType, View } from '@nextcloud/files'
|
||||
import { FileAction, Permission, Node, FileType, View, DefaultType } from '@nextcloud/files'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import ArrowDownSvg from '@mdi/svg/svg/arrow-down.svg?raw'
|
||||
|
||||
|
|
@ -60,6 +60,8 @@ const isDownloadable = function(node: Node) {
|
|||
|
||||
export const action = new FileAction({
|
||||
id: 'download',
|
||||
default: DefaultType.DEFAULT,
|
||||
|
||||
displayName: () => t('files', 'Download'),
|
||||
iconSvgInline: () => ArrowDownSvg,
|
||||
|
||||
|
|
|
|||
|
|
@ -97,9 +97,9 @@ import type { PropType, ShallowRef } from 'vue'
|
|||
import type { FileAction, Node, View } from '@nextcloud/files'
|
||||
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
import { DefaultType, NodeStatus, getFileActions } from '@nextcloud/files'
|
||||
import { DefaultType, NodeStatus } from '@nextcloud/files'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { defineComponent } from 'vue'
|
||||
import { defineComponent, inject } from 'vue'
|
||||
|
||||
import ArrowLeftIcon from 'vue-material-design-icons/ArrowLeft.vue'
|
||||
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
|
||||
|
|
@ -112,9 +112,6 @@ import { useNavigation } from '../../composables/useNavigation'
|
|||
import CustomElementRender from '../CustomElementRender.vue'
|
||||
import logger from '../../logger.js'
|
||||
|
||||
// The registered actions list
|
||||
const actions = getFileActions()
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FileEntryActions',
|
||||
|
||||
|
|
@ -153,10 +150,12 @@ export default defineComponent({
|
|||
|
||||
setup() {
|
||||
const { currentView } = useNavigation()
|
||||
const enabledFileActions = inject<FileAction[]>('enabledFileActions', [])
|
||||
|
||||
return {
|
||||
// The file list is guaranteed to be only shown with active view
|
||||
currentView: currentView as ShallowRef<View>,
|
||||
enabledFileActions,
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -175,23 +174,12 @@ export default defineComponent({
|
|||
return this.source.status === NodeStatus.LOADING
|
||||
},
|
||||
|
||||
// Sorted actions that are enabled for this node
|
||||
enabledActions() {
|
||||
if (this.source.attributes.failed) {
|
||||
return []
|
||||
}
|
||||
|
||||
return actions
|
||||
.filter(action => !action.enabled || action.enabled([this.source], this.currentView))
|
||||
.sort((a, b) => (a.order || 0) - (b.order || 0))
|
||||
},
|
||||
|
||||
// Enabled action that are displayed inline
|
||||
enabledInlineActions() {
|
||||
if (this.filesListWidth < 768 || this.gridMode) {
|
||||
return []
|
||||
}
|
||||
return this.enabledActions.filter(action => action?.inline?.(this.source, this.currentView))
|
||||
return this.enabledFileActions.filter(action => action?.inline?.(this.source, this.currentView))
|
||||
},
|
||||
|
||||
// Enabled action that are displayed inline with a custom render function
|
||||
|
|
@ -199,12 +187,7 @@ export default defineComponent({
|
|||
if (this.gridMode) {
|
||||
return []
|
||||
}
|
||||
return this.enabledActions.filter(action => typeof action.renderInline === 'function')
|
||||
},
|
||||
|
||||
// Default actions
|
||||
enabledDefaultActions() {
|
||||
return this.enabledActions.filter(action => !!action?.default)
|
||||
return this.enabledFileActions.filter(action => typeof action.renderInline === 'function')
|
||||
},
|
||||
|
||||
// Actions shown in the menu
|
||||
|
|
@ -219,7 +202,7 @@ export default defineComponent({
|
|||
// Showing inline first for the NcActions inline prop
|
||||
...this.enabledInlineActions,
|
||||
// Then the rest
|
||||
...this.enabledActions.filter(action => action.default !== DefaultType.HIDDEN && typeof action.renderInline !== 'function'),
|
||||
...this.enabledFileActions.filter(action => action.default !== DefaultType.HIDDEN && typeof action.renderInline !== 'function'),
|
||||
].filter((value, index, self) => {
|
||||
// Then we filter duplicates to prevent inline actions to be shown twice
|
||||
return index === self.findIndex(action => action.id === value.id)
|
||||
|
|
@ -233,7 +216,7 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
enabledSubmenuActions() {
|
||||
return this.enabledActions
|
||||
return this.enabledFileActions
|
||||
.filter(action => action.parent)
|
||||
.reduce((arr, action) => {
|
||||
if (!arr[action.parent!]) {
|
||||
|
|
@ -322,14 +305,6 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
},
|
||||
execDefaultAction(event) {
|
||||
if (this.enabledDefaultActions.length > 0) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
// Execute the first default action if any
|
||||
this.enabledDefaultActions[0].exec(this.source, this.currentView, this.currentDir)
|
||||
}
|
||||
},
|
||||
|
||||
isMenu(id: string) {
|
||||
return this.enabledSubmenuActions[id]?.length > 0
|
||||
|
|
|
|||
|
|
@ -54,20 +54,22 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { Node } from '@nextcloud/files'
|
||||
import type { FileAction, Node } from '@nextcloud/files'
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
import { emit } from '@nextcloud/event-bus'
|
||||
import { FileType, NodeStatus, Permission } from '@nextcloud/files'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import axios from '@nextcloud/axios'
|
||||
import Vue from 'vue'
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
import { emit } from '@nextcloud/event-bus'
|
||||
import { FileType, NodeStatus } from '@nextcloud/files'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { isAxiosError} from 'axios'
|
||||
import Vue, { inject } from 'vue'
|
||||
|
||||
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
|
||||
|
||||
import { useNavigation } from '../../composables/useNavigation'
|
||||
import { useRouteParameters } from '../../composables/useRouteParameters.ts'
|
||||
import { useRenamingStore } from '../../store/renaming.ts'
|
||||
import logger from '../../logger.js'
|
||||
|
||||
|
|
@ -115,10 +117,15 @@ export default Vue.extend({
|
|||
|
||||
setup() {
|
||||
const { currentView } = useNavigation()
|
||||
const { directory } = useRouteParameters()
|
||||
const renamingStore = useRenamingStore()
|
||||
|
||||
const defaultFileAction = inject<FileAction | undefined>('defaultFileAction')
|
||||
|
||||
return {
|
||||
currentView,
|
||||
defaultFileAction,
|
||||
directory,
|
||||
|
||||
renamingStore,
|
||||
}
|
||||
|
|
@ -158,32 +165,20 @@ export default Vue.extend({
|
|||
}
|
||||
}
|
||||
|
||||
const enabledDefaultActions = this.$parent?.$refs?.actions?.enabledDefaultActions
|
||||
if (enabledDefaultActions?.length > 0) {
|
||||
const action = enabledDefaultActions[0]
|
||||
const displayName = action.displayName([this.source], this.currentView)
|
||||
if (this.defaultFileAction && this.currentView) {
|
||||
const displayName = this.defaultFileAction.displayName([this.source], this.currentView)
|
||||
return {
|
||||
is: 'a',
|
||||
is: 'button',
|
||||
params: {
|
||||
'aria-label': displayName,
|
||||
title: displayName,
|
||||
role: 'button',
|
||||
tabindex: '0',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (this.source?.permissions & Permission.READ) {
|
||||
return {
|
||||
is: 'a',
|
||||
params: {
|
||||
download: this.source.basename,
|
||||
href: this.source.source,
|
||||
title: t('files', 'Download file {name}', { name: `${this.basename}${this.extension}` }),
|
||||
tabindex: '0',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// nothing interactive here, there is no default action
|
||||
// so if not even the download action works we only can show the list entry
|
||||
return {
|
||||
is: 'span',
|
||||
}
|
||||
|
|
@ -324,20 +319,25 @@ export default Vue.extend({
|
|||
// Reset the renaming store
|
||||
this.stopRenaming()
|
||||
this.$nextTick(() => {
|
||||
this.$refs.basename.focus()
|
||||
const nameContainter = this.$refs.basename as HTMLElement | undefined
|
||||
nameContainter?.focus()
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error while renaming file', { error })
|
||||
// Rename back as it failed
|
||||
this.source.rename(oldName)
|
||||
this.$refs.renameInput.focus()
|
||||
// And ensure we reset to the renaming state
|
||||
this.startRenaming()
|
||||
|
||||
// TODO: 409 means current folder does not exist, redirect ?
|
||||
if (error?.response?.status === 404) {
|
||||
showError(t('files', 'Could not rename "{oldName}", it does not exist any more', { oldName }))
|
||||
return
|
||||
} else if (error?.response?.status === 412) {
|
||||
showError(t('files', 'The name "{newName}" is already used in the folder "{dir}". Please choose a different name.', { newName, dir: this.currentDir }))
|
||||
return
|
||||
if (isAxiosError(error)) {
|
||||
// TODO: 409 means current folder does not exist, redirect ?
|
||||
if (error?.response?.status === 404) {
|
||||
showError(t('files', 'Could not rename "{oldName}", it does not exist any more', { oldName }))
|
||||
return
|
||||
} else if (error?.response?.status === 412) {
|
||||
showError(t('files', 'The name "{newName}" is already used in the folder "{dir}". Please choose a different name.', { newName, dir: this.directory }))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown error
|
||||
|
|
@ -352,3 +352,16 @@ export default Vue.extend({
|
|||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
button.files-list__row-name-link {
|
||||
background-color: unset;
|
||||
border: none;
|
||||
font-weight: normal;
|
||||
|
||||
&:active {
|
||||
// No active styles - handled by the row entry
|
||||
background-color: unset !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import type { ComponentPublicInstance, PropType } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import type { FileSource } from '../types.ts'
|
||||
|
||||
import { showError } from '@nextcloud/dialogs'
|
||||
import { FileType, Permission, Folder, File as NcFile, NodeStatus, Node, View } from '@nextcloud/files'
|
||||
import { FileType, Permission, Folder, File as NcFile, NodeStatus, Node, getFileActions } from '@nextcloud/files'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { vOnClickOutside } from '@vueuse/components'
|
||||
|
|
@ -36,10 +36,11 @@ import { getDragAndDropPreview } from '../utils/dragUtils.ts'
|
|||
import { hashCode } from '../utils/hashUtils.ts'
|
||||
import { dataTransferToFileTree, onDropExternalFiles, onDropInternalFiles } from '../services/DropService.ts'
|
||||
import logger from '../logger.js'
|
||||
import FileEntryActions from '../components/FileEntry/FileEntryActions.vue'
|
||||
|
||||
Vue.directive('onClickOutside', vOnClickOutside)
|
||||
|
||||
const actions = getFileActions()
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
source: {
|
||||
|
|
@ -56,6 +57,13 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
|
||||
provide() {
|
||||
return {
|
||||
defaultFileAction: this.defaultFileAction,
|
||||
enabledFileActions: this.enabledFileActions,
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: '',
|
||||
|
|
@ -173,6 +181,23 @@ export default defineComponent({
|
|||
isRenaming() {
|
||||
return this.renamingStore.renamingNode === this.source
|
||||
},
|
||||
|
||||
/**
|
||||
* Sorted actions that are enabled for this node
|
||||
*/
|
||||
enabledFileActions() {
|
||||
if (this.source.status === NodeStatus.FAILED) {
|
||||
return []
|
||||
}
|
||||
|
||||
return actions
|
||||
.filter(action => !action.enabled || action.enabled([this.source], this.currentView))
|
||||
.sort((a, b) => (a.order || 0) - (b.order || 0))
|
||||
},
|
||||
|
||||
defaultFileAction() {
|
||||
return this.enabledFileActions.find((action) => action.default !== undefined)
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
|
@ -254,8 +279,15 @@ export default defineComponent({
|
|||
return false
|
||||
}
|
||||
|
||||
const actions = this.$refs.actions as ComponentPublicInstance<typeof FileEntryActions>
|
||||
actions.execDefaultAction(event)
|
||||
if (this.defaultFileAction) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
// Execute the first default action if any
|
||||
this.defaultFileAction.exec(this.source, this.currentView, this.currentDir)
|
||||
} else {
|
||||
// fallback to open in current tab
|
||||
window.open(generateUrl('/f/{fileId}', { fileId: this.fileid }), '_self')
|
||||
}
|
||||
},
|
||||
|
||||
openDetailsIfAvailable(event) {
|
||||
|
|
|
|||
|
|
@ -596,24 +596,26 @@ export default defineComponent({
|
|||
// Take as much space as possible
|
||||
flex: 1 1 auto;
|
||||
|
||||
a {
|
||||
button.files-list__row-name-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: start;
|
||||
// Fill cell height and width
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
// Necessary for flex grow to work
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
|
||||
// Already added to the inner text, see rule below
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
// Keyboard indicator a11y
|
||||
&:focus .files-list__row-name-text {
|
||||
outline: 2px solid var(--color-main-text) !important;
|
||||
border-radius: 20px;
|
||||
outline: var(--border-width-input-focused) solid var(--color-main-text) !important;
|
||||
border-radius: var(--border-radius-element);
|
||||
}
|
||||
&:focus:not(:focus-visible) .files-list__row-name-text {
|
||||
outline: none !important;
|
||||
|
|
@ -623,7 +625,7 @@ export default defineComponent({
|
|||
.files-list__row-name-text {
|
||||
color: var(--color-main-text);
|
||||
// Make some space for the outline
|
||||
padding: 5px 10px;
|
||||
padding: var(--default-grid-baseline) calc(2 * var(--default-grid-baseline));
|
||||
margin-left: -10px;
|
||||
// Align two name and ext
|
||||
display: inline-flex;
|
||||
|
|
@ -764,12 +766,6 @@ tbody.files-list__tbody.files-list__tbody--grid {
|
|||
padding-top: var(--half-clickable-area);
|
||||
}
|
||||
|
||||
a.files-list__row-name-link {
|
||||
// Minus action menu
|
||||
width: calc(100% - var(--clickable-area));
|
||||
height: var(--clickable-area);
|
||||
}
|
||||
|
||||
.files-list__row-name-text {
|
||||
margin: 0;
|
||||
padding-right: 0;
|
||||
|
|
|
|||
50
apps/files/src/composables/useRouteParameters.ts
Normal file
50
apps/files/src/composables/useRouteParameters.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router/composables'
|
||||
|
||||
/**
|
||||
* Get information about the current route
|
||||
*/
|
||||
export function useRouteParameters() {
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
/**
|
||||
* Get the path of the current active directory
|
||||
*/
|
||||
const directory = computed<string>(
|
||||
() => String(route.query.dir || '/')
|
||||
// Remove any trailing slash but leave root slash
|
||||
.replace(/^(.+)\/$/, '$1'),
|
||||
)
|
||||
|
||||
/**
|
||||
* Get the current fileId used on the route
|
||||
*/
|
||||
const fileId = computed<number | null>(() => {
|
||||
const fileId = Number.parseInt(route.params.fileid ?? '0') || null
|
||||
return Number.isNaN(fileId) ? null : fileId
|
||||
})
|
||||
|
||||
/**
|
||||
* State of `openFile` route param
|
||||
*/
|
||||
const openFile = computed<boolean>(
|
||||
// if `openfile` is set it is considered truthy, but allow to explicitly set it to 'false'
|
||||
() => 'openfile' in route.query && (typeof route.query.openfile !== 'string' || route.query.openfile.toLocaleLowerCase() !== 'false'),
|
||||
)
|
||||
|
||||
return {
|
||||
/** Path of currently open directory */
|
||||
directory,
|
||||
|
||||
/** Current active fileId */
|
||||
fileId,
|
||||
|
||||
/** Should the active node should be opened (`openFile` route param) */
|
||||
openFile,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import type { Configuration } from 'webpack'
|
||||
import {
|
||||
applyChangesToNextcloud,
|
||||
configureNextcloud,
|
||||
|
|
@ -6,9 +11,9 @@ import {
|
|||
waitOnNextcloud,
|
||||
} from './cypress/dockerNode'
|
||||
import { defineConfig } from 'cypress'
|
||||
import { removeDirectory } from 'cypress-delete-downloads-folder'
|
||||
import cypressSplit from 'cypress-split'
|
||||
import webpackPreprocessor from '@cypress/webpack-preprocessor'
|
||||
import type { Configuration } from 'webpack'
|
||||
|
||||
import webpackConfig from './webpack.config.js'
|
||||
|
||||
|
|
@ -55,6 +60,8 @@ export default defineConfig({
|
|||
|
||||
on('file:preprocessor', webpackPreprocessor({ webpackOptions: webpackConfig as Configuration }))
|
||||
|
||||
on('task', { removeDirectory })
|
||||
|
||||
// Disable spell checking to prevent rendering differences
|
||||
on('before:browser:launch', (browser, launchOptions) => {
|
||||
if (browser.family === 'chromium' && browser.name !== 'electron') {
|
||||
|
|
|
|||
145
cypress/e2e/files/files-download.cy.ts
Normal file
145
cypress/e2e/files/files-download.cy.ts
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { User } from '@nextcloud/cypress'
|
||||
import { getRowForFile, navigateToFolder, triggerActionForFile } from './FilesUtils'
|
||||
import { deleteDownloadsFolderBeforeEach } from 'cypress-delete-downloads-folder'
|
||||
|
||||
describe('files: Download files using file actions', { testIsolation: true }, () => {
|
||||
let user: User
|
||||
|
||||
deleteDownloadsFolderBeforeEach()
|
||||
|
||||
beforeEach(() => {
|
||||
cy.createRandomUser().then(($user) => {
|
||||
user = $user
|
||||
})
|
||||
})
|
||||
|
||||
it('can download file', () => {
|
||||
cy.uploadContent(user, new Blob(['<content>']), 'text/plain', '/file.txt')
|
||||
cy.login(user)
|
||||
cy.visit('/apps/files')
|
||||
|
||||
getRowForFile('file.txt').should('be.visible')
|
||||
|
||||
triggerActionForFile('file.txt', 'download')
|
||||
|
||||
const downloadsFolder = Cypress.config('downloadsFolder')
|
||||
cy.readFile(`${downloadsFolder}/file.txt`, { timeout: 15000 })
|
||||
.should('exist')
|
||||
.and('have.length.gt', 8)
|
||||
.and('equal', '<content>')
|
||||
})
|
||||
|
||||
/**
|
||||
* Regression test of https://github.com/nextcloud/server/issues/44855
|
||||
*/
|
||||
it('can download file with hash name', () => {
|
||||
cy.uploadContent(user, new Blob(['<content>']), 'text/plain', '/#file.txt')
|
||||
cy.login(user)
|
||||
cy.visit('/apps/files')
|
||||
|
||||
triggerActionForFile('#file.txt', 'download')
|
||||
const downloadsFolder = Cypress.config('downloadsFolder')
|
||||
cy.readFile(`${downloadsFolder}/#file.txt`, { timeout: 15000 })
|
||||
.should('exist')
|
||||
.and('have.length.gt', 8)
|
||||
.and('equal', '<content>')
|
||||
})
|
||||
|
||||
/**
|
||||
* Regression test of https://github.com/nextcloud/server/issues/44855
|
||||
*/
|
||||
it('can download file from folder with hash name', () => {
|
||||
cy.mkdir(user, '/#folder')
|
||||
.uploadContent(user, new Blob(['<content>']), 'text/plain', '/#folder/file.txt')
|
||||
cy.login(user)
|
||||
cy.visit('/apps/files')
|
||||
|
||||
navigateToFolder('#folder')
|
||||
// All are visible by default
|
||||
getRowForFile('file.txt').should('be.visible')
|
||||
|
||||
triggerActionForFile('file.txt', 'download')
|
||||
const downloadsFolder = Cypress.config('downloadsFolder')
|
||||
cy.readFile(`${downloadsFolder}/file.txt`, { timeout: 15000 })
|
||||
.should('exist')
|
||||
.and('have.length.gt', 8)
|
||||
.and('equal', '<content>')
|
||||
})
|
||||
})
|
||||
|
||||
describe('files: Download files using default action', { testIsolation: true }, () => {
|
||||
let user: User
|
||||
|
||||
deleteDownloadsFolderBeforeEach()
|
||||
|
||||
beforeEach(() => {
|
||||
cy.createRandomUser().then(($user) => {
|
||||
user = $user
|
||||
})
|
||||
})
|
||||
|
||||
it('can download file', () => {
|
||||
cy.uploadContent(user, new Blob(['<content>']), 'text/plain', '/file.txt')
|
||||
cy.login(user)
|
||||
cy.visit('/apps/files')
|
||||
|
||||
getRowForFile('file.txt')
|
||||
.should('be.visible')
|
||||
.findByRole('button', { name: 'Download' })
|
||||
.click()
|
||||
|
||||
const downloadsFolder = Cypress.config('downloadsFolder')
|
||||
cy.readFile(`${downloadsFolder}/file.txt`, { timeout: 15000 })
|
||||
.should('exist')
|
||||
.and('have.length.gt', 8)
|
||||
.and('equal', '<content>')
|
||||
})
|
||||
|
||||
/**
|
||||
* Regression test of https://github.com/nextcloud/server/issues/44855
|
||||
*/
|
||||
it('can download file with hash name', () => {
|
||||
cy.uploadContent(user, new Blob(['<content>']), 'text/plain', '/#file.txt')
|
||||
cy.login(user)
|
||||
cy.visit('/apps/files')
|
||||
|
||||
getRowForFile('#file.txt')
|
||||
.should('be.visible')
|
||||
.findByRole('button', { name: 'Download' })
|
||||
.click()
|
||||
|
||||
const downloadsFolder = Cypress.config('downloadsFolder')
|
||||
cy.readFile(`${downloadsFolder}/#file.txt`, { timeout: 15000 })
|
||||
.should('exist')
|
||||
.and('have.length.gt', 8)
|
||||
.and('equal', '<content>')
|
||||
})
|
||||
|
||||
/**
|
||||
* Regression test of https://github.com/nextcloud/server/issues/44855
|
||||
*/
|
||||
it('can download file from folder with hash name', () => {
|
||||
cy.mkdir(user, '/#folder')
|
||||
.uploadContent(user, new Blob(['<content>']), 'text/plain', '/#folder/file.txt')
|
||||
cy.login(user)
|
||||
cy.visit('/apps/files')
|
||||
|
||||
navigateToFolder('#folder')
|
||||
// All are visible by default
|
||||
getRowForFile('file.txt')
|
||||
.should('be.visible')
|
||||
.findByRole('button', { name: 'Download' })
|
||||
.click()
|
||||
|
||||
const downloadsFolder = Cypress.config('downloadsFolder')
|
||||
cy.readFile(`${downloadsFolder}/file.txt`, { timeout: 15000 })
|
||||
.should('exist')
|
||||
.and('have.length.gt', 8)
|
||||
.and('equal', '<content>')
|
||||
})
|
||||
})
|
||||
|
|
@ -35,7 +35,7 @@ describe('files_sharing: Files view', { testIsolation: true }, () => {
|
|||
// see the shared folder
|
||||
getRowForFile('folder').should('be.visible')
|
||||
// click on the folder should open it in files
|
||||
getRowForFile('folder').findByRole('button', { name: 'folder' }).click()
|
||||
getRowForFile('folder').findByRole('button', { name: /open in files/i }).click()
|
||||
// See the URL has changed
|
||||
cy.url().should('match', /apps\/files\/files\/.+dir=\/folder/)
|
||||
// Content of the shared folder
|
||||
|
|
@ -50,7 +50,7 @@ describe('files_sharing: Files view', { testIsolation: true }, () => {
|
|||
// see the shared folder
|
||||
getRowForFile('folder').should('be.visible')
|
||||
// click on the folder should open it in files
|
||||
getRowForFile('folder').findByRole('button', { name: 'folder' }).click()
|
||||
getRowForFile('folder').findByRole('button', { name: /open in files/i }).click()
|
||||
// See the URL has changed
|
||||
cy.url().should('match', /apps\/files\/files\/.+dir=\/folder/)
|
||||
// Content of the shared folder
|
||||
|
|
|
|||
4
dist/core-common.js
vendored
4
dist/core-common.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-common.js.map
vendored
2
dist/core-common.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/core-main.js
vendored
4
dist/core-main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-main.js.map
vendored
2
dist/core-main.js.map
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
4
dist/files-init.js
vendored
4
dist/files-init.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-init.js.map
vendored
2
dist/files-init.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-main.js
vendored
4
dist/files-main.js
vendored
File diff suppressed because one or more lines are too long
5
dist/files-main.js.LICENSE.txt
vendored
5
dist/files-main.js.LICENSE.txt
vendored
|
|
@ -1,3 +1,8 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/*!
|
||||
* pinia v2.1.7
|
||||
* (c) 2023 Eduardo San Martin Morote
|
||||
|
|
|
|||
2
dist/files-main.js.map
vendored
2
dist/files-main.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-personal-settings.js
vendored
4
dist/files-personal-settings.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-personal-settings.js.map
vendored
2
dist/files-personal-settings.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files-sidebar.js
vendored
4
dist/files-sidebar.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-sidebar.js.map
vendored
2
dist/files-sidebar.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files_reminders-init.js
vendored
4
dist/files_reminders-init.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_reminders-init.js.map
vendored
2
dist/files_reminders-init.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files_sharing-files_sharing_tab.js
vendored
4
dist/files_sharing-files_sharing_tab.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_sharing-files_sharing_tab.js.map
vendored
2
dist/files_sharing-files_sharing_tab.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files_sharing-personal-settings.js
vendored
4
dist/files_sharing-personal-settings.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_sharing-personal-settings.js.map
vendored
2
dist/files_sharing-personal-settings.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/files_versions-files_versions.js
vendored
4
dist/files_versions-files_versions.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files_versions-files_versions.js.map
vendored
2
dist/files_versions-files_versions.js.map
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
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
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
1019
package-lock.json
generated
1019
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -147,9 +147,10 @@
|
|||
"css-loader": "^6.8.1",
|
||||
"cypress": "^13.13.2",
|
||||
"cypress-axe": "^1.5.0",
|
||||
"cypress-if": "^1.10.5",
|
||||
"cypress-split": "^1.21.0",
|
||||
"cypress-wait-until": "^2.0.1",
|
||||
"cypress-delete-downloads-folder": "^0.0.6",
|
||||
"cypress-if": "^1.12.5",
|
||||
"cypress-split": "^1.24.0",
|
||||
"cypress-wait-until": "^3.0.2",
|
||||
"dockerode": "^4.0.2",
|
||||
"eslint-plugin-cypress": "^2.15.2",
|
||||
"eslint-plugin-es": "^4.1.0",
|
||||
|
|
|
|||
Loading…
Reference in a new issue