mirror of
https://github.com/nextcloud/server.git
synced 2025-07-31 04:59:18 +00:00
test: add e2e tests for files search
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
@ -10,7 +10,8 @@ import NavigationView from './Navigation.vue'
|
|||||||
import { useViewConfigStore } from '../store/viewConfig'
|
import { useViewConfigStore } from '../store/viewConfig'
|
||||||
import { Folder, View, getNavigation } from '@nextcloud/files'
|
import { Folder, View, getNavigation } from '@nextcloud/files'
|
||||||
|
|
||||||
import router from '../router/router'
|
import router from '../router/router.ts'
|
||||||
|
import RouterService from '../services/RouterService'
|
||||||
|
|
||||||
const resetNavigation = () => {
|
const resetNavigation = () => {
|
||||||
const nav = getNavigation()
|
const nav = getNavigation()
|
||||||
@ -27,9 +28,18 @@ const createView = (id: string, name: string, parent?: string) => new View({
|
|||||||
parent,
|
parent,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function mockWindow() {
|
||||||
|
window.OCP ??= {}
|
||||||
|
window.OCP.Files ??= {}
|
||||||
|
window.OCP.Files.Router = new RouterService(router)
|
||||||
|
}
|
||||||
|
|
||||||
describe('Navigation renders', () => {
|
describe('Navigation renders', () => {
|
||||||
before(() => {
|
before(async () => {
|
||||||
delete window._nc_navigation
|
delete window._nc_navigation
|
||||||
|
mockWindow()
|
||||||
|
getNavigation().register(createView('files', 'Files'))
|
||||||
|
await router.replace({ name: 'filelist', params: { view: 'files' } })
|
||||||
|
|
||||||
cy.mockInitialState('files', 'storageStats', {
|
cy.mockInitialState('files', 'storageStats', {
|
||||||
used: 1000 * 1000 * 1000,
|
used: 1000 * 1000 * 1000,
|
||||||
@ -41,6 +51,7 @@ describe('Navigation renders', () => {
|
|||||||
|
|
||||||
it('renders', () => {
|
it('renders', () => {
|
||||||
cy.mount(NavigationView, {
|
cy.mount(NavigationView, {
|
||||||
|
router,
|
||||||
global: {
|
global: {
|
||||||
plugins: [createTestingPinia({
|
plugins: [createTestingPinia({
|
||||||
createSpy: cy.spy,
|
createSpy: cy.spy,
|
||||||
@ -60,6 +71,7 @@ describe('Navigation API', () => {
|
|||||||
before(async () => {
|
before(async () => {
|
||||||
delete window._nc_navigation
|
delete window._nc_navigation
|
||||||
Navigation = getNavigation()
|
Navigation = getNavigation()
|
||||||
|
mockWindow()
|
||||||
|
|
||||||
await router.replace({ name: 'filelist', params: { view: 'files' } })
|
await router.replace({ name: 'filelist', params: { view: 'files' } })
|
||||||
})
|
})
|
||||||
@ -152,14 +164,18 @@ describe('Navigation API', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('Quota rendering', () => {
|
describe('Quota rendering', () => {
|
||||||
before(() => {
|
before(async () => {
|
||||||
delete window._nc_navigation
|
delete window._nc_navigation
|
||||||
|
mockWindow()
|
||||||
|
getNavigation().register(createView('files', 'Files'))
|
||||||
|
await router.replace({ name: 'filelist', params: { view: 'files' } })
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => cy.unmockInitialState())
|
afterEach(() => cy.unmockInitialState())
|
||||||
|
|
||||||
it('Unknown quota', () => {
|
it('Unknown quota', () => {
|
||||||
cy.mount(NavigationView, {
|
cy.mount(NavigationView, {
|
||||||
|
router,
|
||||||
global: {
|
global: {
|
||||||
plugins: [createTestingPinia({
|
plugins: [createTestingPinia({
|
||||||
createSpy: cy.spy,
|
createSpy: cy.spy,
|
||||||
@ -177,6 +193,7 @@ describe('Quota rendering', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
cy.mount(NavigationView, {
|
cy.mount(NavigationView, {
|
||||||
|
router,
|
||||||
global: {
|
global: {
|
||||||
plugins: [createTestingPinia({
|
plugins: [createTestingPinia({
|
||||||
createSpy: cy.spy,
|
createSpy: cy.spy,
|
||||||
@ -197,6 +214,7 @@ describe('Quota rendering', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
cy.mount(NavigationView, {
|
cy.mount(NavigationView, {
|
||||||
|
router,
|
||||||
global: {
|
global: {
|
||||||
plugins: [createTestingPinia({
|
plugins: [createTestingPinia({
|
||||||
createSpy: cy.spy,
|
createSpy: cy.spy,
|
||||||
@ -219,6 +237,7 @@ describe('Quota rendering', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
cy.mount(NavigationView, {
|
cy.mount(NavigationView, {
|
||||||
|
router,
|
||||||
global: {
|
global: {
|
||||||
plugins: [createTestingPinia({
|
plugins: [createTestingPinia({
|
||||||
createSpy: cy.spy,
|
createSpy: cy.spy,
|
||||||
|
198
cypress/e2e/files/search.cy.ts
Normal file
198
cypress/e2e/files/search.cy.ts
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
/*!
|
||||||
|
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { User } from '@nextcloud/cypress'
|
||||||
|
import { FilesNavigationPage } from '../../pages/FilesNavigation'
|
||||||
|
import { getRowForFile, navigateToFolder } from './FilesUtils'
|
||||||
|
|
||||||
|
describe('files: search', () => {
|
||||||
|
|
||||||
|
let user: User
|
||||||
|
|
||||||
|
const navigation = new FilesNavigationPage()
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
cy.createRandomUser().then(($user) => {
|
||||||
|
user = $user
|
||||||
|
cy.mkdir(user, '/some folder')
|
||||||
|
cy.mkdir(user, '/other folder')
|
||||||
|
cy.mkdir(user, '/12345')
|
||||||
|
cy.uploadContent(user, new Blob(['content']), 'text/plain', '/file.txt')
|
||||||
|
cy.uploadContent(user, new Blob(['content']), 'text/plain', '/some folder/a file.txt')
|
||||||
|
cy.uploadContent(user, new Blob(['content']), 'text/plain', '/some folder/a second file.txt')
|
||||||
|
cy.uploadContent(user, new Blob(['content']), 'text/plain', '/other folder/another file.txt')
|
||||||
|
cy.login(user)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/apps/files')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('updates the query on the URL', () => {
|
||||||
|
navigation.searchScopeTrigger().click()
|
||||||
|
navigation.searchScopeMenu()
|
||||||
|
.should('be.visible')
|
||||||
|
.findByRole('menuitem', { name: /search globally/i })
|
||||||
|
.should('be.visible')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
navigation.searchInput().type('file')
|
||||||
|
cy.url().should('match', /query=file($|&)/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can search globally', () => {
|
||||||
|
navigation.searchScopeTrigger().click()
|
||||||
|
navigation.searchScopeMenu()
|
||||||
|
.should('be.visible')
|
||||||
|
.findByRole('menuitem', { name: /search globally/i })
|
||||||
|
.should('be.visible')
|
||||||
|
.click()
|
||||||
|
navigation.searchInput().type('file')
|
||||||
|
|
||||||
|
getRowForFile('file.txt').should('be.visible')
|
||||||
|
getRowForFile('a file.txt').should('be.visible')
|
||||||
|
getRowForFile('a second file.txt').should('be.visible')
|
||||||
|
getRowForFile('another file.txt').should('be.visible')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can search locally', () => {
|
||||||
|
navigateToFolder('some folder')
|
||||||
|
getRowForFile('a file.txt').should('be.visible')
|
||||||
|
|
||||||
|
navigation.searchScopeTrigger().click()
|
||||||
|
navigation.searchScopeMenu()
|
||||||
|
.should('be.visible')
|
||||||
|
.findByRole('menuitem', { name: /search from this location/i })
|
||||||
|
.should('be.visible')
|
||||||
|
.click()
|
||||||
|
navigation.searchInput().type('file')
|
||||||
|
|
||||||
|
getRowForFile('a file.txt').should('be.visible')
|
||||||
|
getRowForFile('a second file.txt').should('be.visible')
|
||||||
|
cy.get('[data-cy-files-list-row-fileid]').should('have.length', 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows empty content when there are no results', () => {
|
||||||
|
navigateToFolder('some folder')
|
||||||
|
getRowForFile('a file.txt').should('be.visible')
|
||||||
|
|
||||||
|
navigation.searchScopeTrigger().click()
|
||||||
|
navigation.searchScopeMenu()
|
||||||
|
.should('be.visible')
|
||||||
|
.findByRole('menuitem', { name: /search from this location/i })
|
||||||
|
.should('be.visible')
|
||||||
|
.click()
|
||||||
|
navigation.searchInput().type('folder')
|
||||||
|
|
||||||
|
// see the empty content message
|
||||||
|
cy.contains('[role="note"]', /No search results for .folder./)
|
||||||
|
.should('be.visible')
|
||||||
|
.within(() => {
|
||||||
|
// see within there is a search box with the same value
|
||||||
|
cy.findByRole('searchbox', { name: /search for files/i })
|
||||||
|
.should('be.visible')
|
||||||
|
.and('have.value', 'folder')
|
||||||
|
// and we can switch from local to global search
|
||||||
|
cy.findByRole('button', { name: 'Search globally' })
|
||||||
|
.should('be.visible')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can turn local search into global search', () => {
|
||||||
|
navigateToFolder('some folder')
|
||||||
|
getRowForFile('a file.txt').should('be.visible')
|
||||||
|
|
||||||
|
navigation.searchScopeTrigger().click()
|
||||||
|
navigation.searchScopeMenu()
|
||||||
|
.should('be.visible')
|
||||||
|
.findByRole('menuitem', { name: /search from this location/i })
|
||||||
|
.should('be.visible')
|
||||||
|
.click()
|
||||||
|
navigation.searchInput().type('folder')
|
||||||
|
|
||||||
|
// see the empty content message and turn into global search
|
||||||
|
cy.contains('[role="note"]', /No search results for .folder./)
|
||||||
|
.should('be.visible')
|
||||||
|
.findByRole('button', { name: 'Search globally' })
|
||||||
|
.should('be.visible')
|
||||||
|
.click()
|
||||||
|
|
||||||
|
getRowForFile('some folder').should('be.visible')
|
||||||
|
getRowForFile('other folder').should('be.visible')
|
||||||
|
cy.get('[data-cy-files-list-row-fileid]').should('have.length', 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('can alter search', () => {
|
||||||
|
navigation.searchScopeTrigger().click()
|
||||||
|
navigation.searchScopeMenu()
|
||||||
|
.should('be.visible')
|
||||||
|
.findByRole('menuitem', { name: /search globally/i })
|
||||||
|
.should('be.visible')
|
||||||
|
.click()
|
||||||
|
navigation.searchInput().type('other')
|
||||||
|
|
||||||
|
getRowForFile('another file.txt').should('be.visible')
|
||||||
|
getRowForFile('other folder').should('be.visible')
|
||||||
|
cy.get('[data-cy-files-list-row-fileid]').should('have.length', 2)
|
||||||
|
|
||||||
|
navigation.searchInput().type(' file')
|
||||||
|
navigation.searchInput().should('have.value', 'other file')
|
||||||
|
getRowForFile('another file.txt').should('be.visible')
|
||||||
|
cy.get('[data-cy-files-list-row-fileid]').should('have.length', 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns to file list if search is cleared', () => {
|
||||||
|
navigation.searchScopeTrigger().click()
|
||||||
|
navigation.searchScopeMenu()
|
||||||
|
.should('be.visible')
|
||||||
|
.findByRole('menuitem', { name: /search globally/i })
|
||||||
|
.should('be.visible')
|
||||||
|
.click()
|
||||||
|
navigation.searchInput().type('other')
|
||||||
|
|
||||||
|
getRowForFile('another file.txt').should('be.visible')
|
||||||
|
getRowForFile('other folder').should('be.visible')
|
||||||
|
cy.get('[data-cy-files-list-row-fileid]').should('have.length', 2)
|
||||||
|
|
||||||
|
navigation.searchClearButton().click()
|
||||||
|
navigation.searchInput().should('have.value', '')
|
||||||
|
getRowForFile('file.txt').should('be.visible')
|
||||||
|
cy.get('[data-cy-files-list-row-fileid]').should('have.length', 5)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Problem:
|
||||||
|
* 1. Being on the search view
|
||||||
|
* 2. Press the refresh button (name of the current view)
|
||||||
|
* 3. See that the router link does not preserve the query
|
||||||
|
*
|
||||||
|
* We fix this with a navigation guard and need to verify that it works
|
||||||
|
*/
|
||||||
|
it('keeps the query in the URL', () => {
|
||||||
|
navigation.searchScopeTrigger().click()
|
||||||
|
navigation.searchScopeMenu()
|
||||||
|
.should('be.visible')
|
||||||
|
.findByRole('menuitem', { name: /search globally/i })
|
||||||
|
.should('be.visible')
|
||||||
|
.click()
|
||||||
|
navigation.searchInput().type('file')
|
||||||
|
|
||||||
|
// see that the search view is loaded
|
||||||
|
getRowForFile('a file.txt').should('be.visible')
|
||||||
|
// see the correct url
|
||||||
|
cy.url().should('match', /query=file($|&)/)
|
||||||
|
|
||||||
|
cy.intercept('SEARCH', '**/remote.php/dav/').as('search')
|
||||||
|
// refresh the view
|
||||||
|
cy.findByRole('button', { description: /reload current directory/i }).click()
|
||||||
|
// wait for the request
|
||||||
|
cy.wait('@search')
|
||||||
|
// see that the search view is reloaded
|
||||||
|
getRowForFile('a file.txt').should('be.visible')
|
||||||
|
// see the correct url
|
||||||
|
cy.url().should('match', /query=file($|&)/)
|
||||||
|
})
|
||||||
|
})
|
@ -13,7 +13,18 @@ export class FilesNavigationPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
searchInput() {
|
searchInput() {
|
||||||
return this.navigation().findByRole('searchbox', { name: /filter file names/i })
|
return this.navigation().findByRole('searchbox')
|
||||||
|
}
|
||||||
|
|
||||||
|
searchScopeTrigger() {
|
||||||
|
return this.navigation().findByRole('button', { name: /search scope options/i })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only available after clicking on the search scope trigger
|
||||||
|
*/
|
||||||
|
searchScopeMenu() {
|
||||||
|
return cy.findByRole('menu', { name: /search scope options/i })
|
||||||
}
|
}
|
||||||
|
|
||||||
searchClearButton() {
|
searchClearButton() {
|
||||||
|
Reference in New Issue
Block a user