mirror of
https://github.com/nextcloud/server.git
synced 2026-04-15 22:11:17 -04:00
test: move integration testing of hotkeys to Cypress
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
24b3059de7
commit
b63aca792f
3 changed files with 139 additions and 55 deletions
|
|
@ -4,18 +4,14 @@
|
|||
*/
|
||||
|
||||
import type { View } from '@nextcloud/files'
|
||||
import type { Mock } from 'vitest'
|
||||
import type { Location } from 'vue-router'
|
||||
|
||||
import axios from '@nextcloud/axios'
|
||||
import { File, Folder, Permission } from '@nextcloud/files'
|
||||
import { File, Folder, Permission, registerFileAction } from '@nextcloud/files'
|
||||
import { enableAutoDestroy, mount } from '@vue/test-utils'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { defineComponent, nextTick } from 'vue'
|
||||
import { action as deleteAction } from '../actions/deleteAction.ts'
|
||||
import { action as favoriteAction } from '../actions/favoriteAction.ts'
|
||||
import { action as renameAction } from '../actions/renameAction.ts'
|
||||
import { action as sidebarAction } from '../actions/sidebarAction.ts'
|
||||
import { useActiveStore } from '../store/active.ts'
|
||||
import { useFilesStore } from '../store/files.ts'
|
||||
import { getPinia } from '../store/index.ts'
|
||||
|
|
@ -63,10 +59,23 @@ const TestComponent = defineComponent({
|
|||
template: '<div />',
|
||||
})
|
||||
|
||||
beforeAll(() => {
|
||||
// @ts-expect-error mocking for tests
|
||||
window.OCP ??= {}
|
||||
// @ts-expect-error mocking for tests
|
||||
window.OCP.Files ??= {}
|
||||
// @ts-expect-error mocking for tests
|
||||
window.OCP.Files.Router ??= {
|
||||
...router,
|
||||
goToRoute: vi.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
describe('HotKeysService testing', () => {
|
||||
const activeStore = useActiveStore(getPinia())
|
||||
|
||||
let initialState: HTMLInputElement
|
||||
let component: ReturnType<typeof mount>
|
||||
|
||||
enableAutoDestroy(afterEach)
|
||||
|
||||
|
|
@ -114,54 +123,15 @@ describe('HotKeysService testing', () => {
|
|||
})))
|
||||
document.body.appendChild(initialState)
|
||||
|
||||
mount(TestComponent)
|
||||
component = mount(TestComponent)
|
||||
})
|
||||
|
||||
it('Pressing d should open the sidebar once', () => {
|
||||
dispatchEvent({ key: 'd', code: 'KeyD' })
|
||||
// tests for register action handling
|
||||
|
||||
// Modifier keys should not trigger the action
|
||||
dispatchEvent({ key: 'd', code: 'KeyD', ctrlKey: true })
|
||||
dispatchEvent({ key: 'd', code: 'KeyD', altKey: true })
|
||||
dispatchEvent({ key: 'd', code: 'KeyD', shiftKey: true })
|
||||
dispatchEvent({ key: 'd', code: 'KeyD', metaKey: true })
|
||||
|
||||
expect(sidebarAction.enabled).toHaveReturnedWith(true)
|
||||
expect(sidebarAction.exec).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('Pressing F2 should rename the file', () => {
|
||||
dispatchEvent({ key: 'F2', code: 'F2' })
|
||||
|
||||
// Modifier keys should not trigger the action
|
||||
dispatchEvent({ key: 'F2', code: 'F2', ctrlKey: true })
|
||||
dispatchEvent({ key: 'F2', code: 'F2', altKey: true })
|
||||
dispatchEvent({ key: 'F2', code: 'F2', shiftKey: true })
|
||||
dispatchEvent({ key: 'F2', code: 'F2', metaKey: true })
|
||||
|
||||
expect(renameAction.enabled).toHaveReturnedWith(true)
|
||||
expect(renameAction.exec).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('Pressing s should toggle favorite', () => {
|
||||
(favoriteAction.enabled as Mock).mockReturnValue(true);
|
||||
(favoriteAction.exec as Mock).mockImplementationOnce(() => Promise.resolve(null))
|
||||
|
||||
vi.spyOn(axios, 'post').mockImplementationOnce(() => Promise.resolve())
|
||||
dispatchEvent({ key: 's', code: 'KeyS' })
|
||||
|
||||
// Modifier keys should not trigger the action
|
||||
dispatchEvent({ key: 's', code: 'KeyS', ctrlKey: true })
|
||||
dispatchEvent({ key: 's', code: 'KeyS', altKey: true })
|
||||
dispatchEvent({ key: 's', code: 'KeyS', shiftKey: true })
|
||||
dispatchEvent({ key: 's', code: 'KeyS', metaKey: true })
|
||||
|
||||
expect(favoriteAction.exec).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('Pressing Delete should delete the file', async () => {
|
||||
// @ts-expect-error unit testing - private method access
|
||||
vi.spyOn(deleteAction._action, 'exec').mockResolvedValue(() => true)
|
||||
it('registeres actions', () => {
|
||||
component.destroy()
|
||||
registerFileAction(deleteAction)
|
||||
component = mount(TestComponent)
|
||||
|
||||
dispatchEvent({ key: 'Delete', code: 'Delete' })
|
||||
|
||||
|
|
@ -175,6 +145,8 @@ describe('HotKeysService testing', () => {
|
|||
expect(deleteAction.exec).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
// actions implemented by the composable
|
||||
|
||||
it('Pressing alt+up should go to parent directory', () => {
|
||||
expect(router.push).toHaveBeenCalledTimes(0)
|
||||
dispatchEvent({ key: 'ArrowUp', code: 'ArrowUp', altKey: true })
|
||||
|
|
@ -197,9 +169,8 @@ describe('HotKeysService testing', () => {
|
|||
it.each([
|
||||
['ctrlKey'],
|
||||
['altKey'],
|
||||
// those meta keys are still triggering...
|
||||
// ['shiftKey'],
|
||||
// ['metaKey']
|
||||
['shiftKey'],
|
||||
['metaKey'],
|
||||
])('Pressing v with modifier key %s should not toggle grid view', async (modifier: string) => {
|
||||
vi.spyOn(axios, 'put').mockImplementationOnce(() => Promise.resolve())
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import type { User } from '@nextcloud/e2e-test-server/cypress'
|
|||
|
||||
const ACTION_COPY_MOVE = 'move-copy'
|
||||
|
||||
export const getRowForFileId = (fileid: number) => cy.get(`[data-cy-files-list-row-fileid="${fileid}"]`)
|
||||
export const getRowForFileId = (fileid: string | 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)}"]`)
|
||||
|
||||
export const getActionsForFileId = (fileid: number) => getRowForFileId(fileid).find('[data-cy-files-list-row-actions]')
|
||||
|
|
|
|||
113
cypress/e2e/files/hotkeys.cy.ts
Normal file
113
cypress/e2e/files/hotkeys.cy.ts
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { getRowForFileId } from './FilesUtils.ts'
|
||||
|
||||
describe('Files hotkey handling', () => {
|
||||
before(() => {
|
||||
cy.createRandomUser().then((user) => {
|
||||
cy.mkdir(user, '/abcd')
|
||||
cy.mkdir(user, '/zyx')
|
||||
cy.rm(user, '/welcome.txt')
|
||||
cy.login(user)
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(() => cy.visit('/apps/files'))
|
||||
|
||||
it('Pressing "arrow down" should go to first file', () => {
|
||||
cy.get('[data-cy-files-list]')
|
||||
.press(Cypress.Keyboard.Keys.DOWN)
|
||||
|
||||
cy.url()
|
||||
.should('match', /\/apps\/files\/files\/\d+/)
|
||||
.then((url) => new URL(url).pathname.split('/').at(-1))
|
||||
.then((fileId) => getRowForFileId(fileId)
|
||||
.should('exist')
|
||||
.and('have.attr', 'data-cy-files-list-row-name', 'abcd'))
|
||||
})
|
||||
|
||||
it('Pressing "arrow up" should go to first file', () => {
|
||||
cy.get('[data-cy-files-list]')
|
||||
.press(Cypress.Keyboard.Keys.UP)
|
||||
|
||||
cy.url()
|
||||
.should('match', /\/apps\/files\/files\/\d+/)
|
||||
.then((url) => new URL(url).pathname.split('/').at(-1))
|
||||
.then((fileId) => getRowForFileId(fileId)
|
||||
.should('exist')
|
||||
.and('have.attr', 'data-cy-files-list-row-name', 'zyx'))
|
||||
})
|
||||
|
||||
it('Pressing D should open the sidebar once', () => {
|
||||
activateFirstRow()
|
||||
cy.get('[data-cy-files-list]')
|
||||
.press('d')
|
||||
|
||||
cy.get('[data-cy-sidebar]')
|
||||
.should('exist')
|
||||
.and('be.visible')
|
||||
})
|
||||
|
||||
it('Pressing F2 should rename the file', () => {
|
||||
activateFirstRow()
|
||||
cy.get('[data-cy-files-list]')
|
||||
.should('exist')
|
||||
.then(($el) => {
|
||||
const el = $el.get(0)
|
||||
// manually dispatch as Cypress refuses to press F-keys for "security reasons"
|
||||
cy.log('Dispatching F2 keydown/keyup events')
|
||||
el.dispatchEvent(new KeyboardEvent('keydown', { key: 'F2', code: 'F2', bubbles: true }))
|
||||
el.dispatchEvent(new KeyboardEvent('keyup', { key: 'F2', code: 'F2', bubbles: true }))
|
||||
el.dispatchEvent(new KeyboardEvent('keypress', { key: 'F2', code: 'F2', bubbles: true }))
|
||||
})
|
||||
|
||||
cy.get('[data-cy-files-list-row-name]')
|
||||
.first()
|
||||
.findByRole('textbox', { name: /Folder name/ })
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Pressing S should toggle favorite', () => {
|
||||
activateFirstRow()
|
||||
cy.get('[data-cy-files-list]')
|
||||
.press('s')
|
||||
|
||||
cy.get('[data-cy-files-list-row-name]')
|
||||
.first()
|
||||
.as('firstRow')
|
||||
.findByRole('img', { name: /Favorite/ })
|
||||
.should('exist')
|
||||
|
||||
cy.get('[data-cy-files-list]')
|
||||
.press('s')
|
||||
|
||||
cy.get('@firstRow')
|
||||
.findByRole('img', { name: /Favorite/ })
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
it('Pressing DELETE should delete the folder', () => {
|
||||
activateFirstRow()
|
||||
cy.get('td[data-cy-files-list-row-name]')
|
||||
.should('have.length', 2)
|
||||
|
||||
cy.get('[data-cy-files-list]')
|
||||
.press(Cypress.Keyboard.Keys.DELETE)
|
||||
|
||||
cy.get('td[data-cy-files-list-row-name]')
|
||||
.should('have.length', 1)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Activates the first row in the files list by simulating a press of the down arrow key.
|
||||
*/
|
||||
function activateFirstRow() {
|
||||
cy.get('[data-cy-files-list]')
|
||||
.press(Cypress.Keyboard.Keys.DOWN)
|
||||
cy.url()
|
||||
.should('match', /\/apps\/files\/files\/\d+/)
|
||||
}
|
||||
Loading…
Reference in a new issue