mirror of
https://github.com/nextcloud/server.git
synced 2026-04-20 22:00:39 -04:00
test: Add cypress tests for file list filtering
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
1fc6b79f7c
commit
2c0e2cc09e
7 changed files with 340 additions and 125 deletions
|
|
@ -4,14 +4,15 @@
|
|||
-->
|
||||
<template>
|
||||
<div class="file-list-filters">
|
||||
<div class="file-list-filters__filter">
|
||||
<div class="file-list-filters__filter" data-cy-files-filters>
|
||||
<span v-for="filter of visualFilters"
|
||||
:key="filter.id"
|
||||
ref="filterElements" />
|
||||
</div>
|
||||
<ul v-if="activeChips.length > 0" class="file-list-filters__active" :aria-label="t('files', 'Active filters')">
|
||||
<li v-for="(chip, index) of activeChips" :key="index">
|
||||
<NcChip :icon-svg="chip.icon"
|
||||
<NcChip :aria-label-close="t('files', 'Remove filter')"
|
||||
:icon-svg="chip.icon"
|
||||
:text="chip.text"
|
||||
@close="chip.onclick" />
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
-->
|
||||
<template>
|
||||
<NcAppNavigationItem v-if="storageStats"
|
||||
:aria-label="t('files', 'Storage informations')"
|
||||
:aria-description="t('files', 'Storage information')"
|
||||
:class="{ 'app-navigation-entry__settings-quota--not-unlimited': storageStats.quota >= 0}"
|
||||
:loading="loadingStorageStats"
|
||||
:name="storageStatsTitle"
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
<!-- Progress bar -->
|
||||
<NcProgressBar v-if="storageStats.quota >= 0"
|
||||
slot="extra"
|
||||
:aria-label="t('files', 'Storage quota')"
|
||||
:error="storageStats.relative > 80"
|
||||
:value="Math.min(storageStats.relative, 100)" />
|
||||
</NcAppNavigationItem>
|
||||
|
|
|
|||
|
|
@ -8,33 +8,40 @@
|
|||
<template #search>
|
||||
<NcAppNavigationSearch v-model="searchQuery" :label="t('files', 'Filter filenames…')" />
|
||||
</template>
|
||||
<template #list>
|
||||
<NcAppNavigationItem v-for="view in parentViews"
|
||||
:key="view.id"
|
||||
:allow-collapse="true"
|
||||
:data-cy-files-navigation-item="view.id"
|
||||
:exact="useExactRouteMatching(view)"
|
||||
:icon="view.iconClass"
|
||||
:name="view.name"
|
||||
:open="isExpanded(view)"
|
||||
:pinned="view.sticky"
|
||||
:to="generateToNavigation(view)"
|
||||
@update:open="onToggleExpand(view)">
|
||||
<!-- Sanitized icon as svg if provided -->
|
||||
<NcIconSvgWrapper v-if="view.icon" slot="icon" :svg="view.icon" />
|
||||
|
||||
<!-- Child views if any -->
|
||||
<NcAppNavigationItem v-for="child in childViews[view.id]"
|
||||
:key="child.id"
|
||||
:data-cy-files-navigation-item="child.id"
|
||||
:exact-path="true"
|
||||
:icon="child.iconClass"
|
||||
:name="child.name"
|
||||
:to="generateToNavigation(child)">
|
||||
<template #default>
|
||||
<NcAppNavigationList :aria-label="t('files', 'Views')">
|
||||
<NcAppNavigationItem v-for="view in parentViews"
|
||||
:key="view.id"
|
||||
:allow-collapse="true"
|
||||
:data-cy-files-navigation-item="view.id"
|
||||
:exact="useExactRouteMatching(view)"
|
||||
:icon="view.iconClass"
|
||||
:name="view.name"
|
||||
:open="isExpanded(view)"
|
||||
:pinned="view.sticky"
|
||||
:to="generateToNavigation(view)"
|
||||
@update:open="onToggleExpand(view)">
|
||||
<!-- Sanitized icon as svg if provided -->
|
||||
<NcIconSvgWrapper v-if="child.icon" slot="icon" :svg="child.icon" />
|
||||
<NcIconSvgWrapper v-if="view.icon" slot="icon" :svg="view.icon" />
|
||||
|
||||
<!-- Child views if any -->
|
||||
<NcAppNavigationItem v-for="child in childViews[view.id]"
|
||||
:key="child.id"
|
||||
:data-cy-files-navigation-item="child.id"
|
||||
:exact-path="true"
|
||||
:icon="child.iconClass"
|
||||
:name="child.name"
|
||||
:to="generateToNavigation(child)">
|
||||
<!-- Sanitized icon as svg if provided -->
|
||||
<NcIconSvgWrapper v-if="child.icon" slot="icon" :svg="child.icon" />
|
||||
</NcAppNavigationItem>
|
||||
</NcAppNavigationItem>
|
||||
</NcAppNavigationItem>
|
||||
</NcAppNavigationList>
|
||||
|
||||
<!-- Settings modal-->
|
||||
<SettingsModal :open="settingsOpened"
|
||||
data-cy-files-navigation-settings
|
||||
@close="onSettingsClose" />
|
||||
</template>
|
||||
|
||||
<!-- Non-scrollable navigation bottom elements -->
|
||||
|
|
@ -44,19 +51,13 @@
|
|||
<NavigationQuota />
|
||||
|
||||
<!-- Files settings modal toggle-->
|
||||
<NcAppNavigationItem :aria-label="t('files', 'Open the files app settings')"
|
||||
:name="t('files', 'Files settings')"
|
||||
<NcAppNavigationItem :name="t('files', 'Files settings')"
|
||||
data-cy-files-navigation-settings-button
|
||||
@click.prevent.stop="openSettings">
|
||||
<IconCog slot="icon" :size="20" />
|
||||
</NcAppNavigationItem>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<!-- Settings modal-->
|
||||
<SettingsModal :open="settingsOpened"
|
||||
data-cy-files-navigation-settings
|
||||
@close="onSettingsClose" />
|
||||
</NcAppNavigation>
|
||||
</template>
|
||||
|
||||
|
|
@ -70,6 +71,7 @@ import { defineComponent } from 'vue'
|
|||
import IconCog from 'vue-material-design-icons/Cog.vue'
|
||||
import NcAppNavigation from '@nextcloud/vue/dist/Components/NcAppNavigation.js'
|
||||
import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js'
|
||||
import NcAppNavigationList from '@nextcloud/vue/dist/Components/NcAppNavigationList.js'
|
||||
import NcAppNavigationSearch from '@nextcloud/vue/dist/Components/NcAppNavigationSearch.js'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
|
||||
import NavigationQuota from '../components/NavigationQuota.vue'
|
||||
|
|
@ -90,6 +92,7 @@ export default defineComponent({
|
|||
NavigationQuota,
|
||||
NcAppNavigation,
|
||||
NcAppNavigationItem,
|
||||
NcAppNavigationList,
|
||||
NcAppNavigationSearch,
|
||||
NcIconSvgWrapper,
|
||||
SettingsModal,
|
||||
|
|
|
|||
231
cypress/e2e/files/files-filtering.cy.ts
Normal file
231
cypress/e2e/files/files-filtering.cy.ts
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
/**
|
||||
* 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 } from './FilesUtils'
|
||||
import { FilesNavigationPage } from '../../pages/FilesNavigation'
|
||||
import { FilesFilterPage } from '../../pages/FilesFilters'
|
||||
|
||||
describe('files: Filter in files list', { testIsolation: true }, () => {
|
||||
const appNavigation = new FilesNavigationPage()
|
||||
const filesFilters = new FilesFilterPage()
|
||||
let user: User
|
||||
|
||||
beforeEach(() => cy.createRandomUser().then(($user) => {
|
||||
user = $user
|
||||
|
||||
cy.mkdir(user, '/folder')
|
||||
cy.uploadContent(user, new Blob([]), 'text/plain', '/file.txt')
|
||||
cy.uploadContent(user, new Blob([]), 'text/csv', '/spreadsheet.csv')
|
||||
cy.uploadContent(user, new Blob([]), 'text/plain', '/folder/text.txt')
|
||||
cy.login(user)
|
||||
cy.visit('/apps/files')
|
||||
}))
|
||||
|
||||
it('filters current view by name', () => {
|
||||
// All are visible by default
|
||||
getRowForFile('folder').should('be.visible')
|
||||
getRowForFile('file.txt').should('be.visible')
|
||||
|
||||
// Set up a search query
|
||||
appNavigation.searchInput()
|
||||
.type('folder')
|
||||
|
||||
// See that only the folder is visible
|
||||
getRowForFile('folder').should('be.visible')
|
||||
getRowForFile('file.txt').should('not.exist')
|
||||
getRowForFile('spreadsheet.csv').should('not.exist')
|
||||
})
|
||||
|
||||
it('can reset name filter', () => {
|
||||
// All are visible by default
|
||||
getRowForFile('folder').should('be.visible')
|
||||
getRowForFile('file.txt').should('be.visible')
|
||||
|
||||
// Set up a search query
|
||||
appNavigation.searchInput()
|
||||
.type('folder')
|
||||
|
||||
// See that only the folder is visible
|
||||
getRowForFile('folder').should('be.visible')
|
||||
getRowForFile('file.txt').should('not.exist')
|
||||
|
||||
// reset the filter
|
||||
appNavigation.searchInput().should('have.value', 'folder')
|
||||
appNavigation.searchClearButton().should('exist').click()
|
||||
appNavigation.searchInput().should('have.value', '')
|
||||
|
||||
// All are visible again
|
||||
getRowForFile('folder').should('be.visible')
|
||||
getRowForFile('file.txt').should('be.visible')
|
||||
})
|
||||
|
||||
it('filters current view by type', () => {
|
||||
// All are visible by default
|
||||
getRowForFile('folder').should('be.visible')
|
||||
getRowForFile('file.txt').should('be.visible')
|
||||
getRowForFile('spreadsheet.csv').should('be.visible')
|
||||
|
||||
filesFilters.filterContainter()
|
||||
.findByRole('button', { name: 'Type' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
cy.findByRole('menuitemcheckbox', { name: 'Spreadsheets' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
filesFilters.filterContainter()
|
||||
.findByRole('button', { name: 'Type' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
// See that only the spreadsheet is visible
|
||||
getRowForFile('spreadsheet.csv').should('be.visible')
|
||||
getRowForFile('file.txt').should('not.exist')
|
||||
getRowForFile('folder').should('not.exist')
|
||||
})
|
||||
|
||||
it('can reset filter by type', () => {
|
||||
// All are visible by default
|
||||
getRowForFile('folder').should('be.visible')
|
||||
|
||||
filesFilters.filterContainter()
|
||||
.findByRole('button', { name: 'Type' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
cy.findByRole('menuitemcheckbox', { name: 'Spreadsheets' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
filesFilters.filterContainter()
|
||||
.findByRole('button', { name: 'Type' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
// See folder is not visible
|
||||
getRowForFile('folder').should('not.exist')
|
||||
|
||||
// clear filter
|
||||
filesFilters.filterContainter()
|
||||
.findByRole('button', { name: 'Type' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
cy.findByRole('menuitem', { name: /clear filter/i })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
filesFilters.filterContainter()
|
||||
.findByRole('button', { name: 'Type' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
// See folder is visible again
|
||||
getRowForFile('folder').should('be.visible')
|
||||
})
|
||||
|
||||
it('can reset filter by clicking chip button', () => {
|
||||
// All are visible by default
|
||||
getRowForFile('folder').should('be.visible')
|
||||
|
||||
filesFilters.filterContainter()
|
||||
.findByRole('button', { name: 'Type' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
cy.findByRole('menuitemcheckbox', { name: 'Spreadsheets' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
filesFilters.filterContainter()
|
||||
.findByRole('button', { name: 'Type' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
|
||||
// See folder is not visible
|
||||
getRowForFile('folder').should('not.exist')
|
||||
|
||||
// clear filter
|
||||
filesFilters.removeFilter('Spreadsheets')
|
||||
|
||||
// See folder is visible again
|
||||
getRowForFile('folder').should('be.visible')
|
||||
})
|
||||
|
||||
it('keeps name filter when changing the directory', () => {
|
||||
// All are visible by default
|
||||
getRowForFile('folder').should('be.visible')
|
||||
getRowForFile('file.txt').should('be.visible')
|
||||
|
||||
// Set up a search query
|
||||
appNavigation.searchInput()
|
||||
.type('folder')
|
||||
|
||||
// See that only the folder is visible
|
||||
getRowForFile('folder').should('be.visible')
|
||||
getRowForFile('file.txt').should('not.exist')
|
||||
|
||||
// go to that folder
|
||||
navigateToFolder('folder')
|
||||
|
||||
// see that the folder is also filtered
|
||||
getRowForFile('text.txt').should('not.exist')
|
||||
})
|
||||
|
||||
it('keeps type filter when changing the directory', () => {
|
||||
// All are visible by default
|
||||
getRowForFile('folder').should('be.visible')
|
||||
getRowForFile('file.txt').should('be.visible')
|
||||
|
||||
filesFilters.filterContainter()
|
||||
.findByRole('button', { name: 'Type' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
cy.findByRole('menuitemcheckbox', { name: 'Folders' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
filesFilters.filterContainter()
|
||||
.findByRole('button', { name: 'Type' })
|
||||
.click()
|
||||
|
||||
// See that only the folder is visible
|
||||
getRowForFile('folder').should('be.visible')
|
||||
getRowForFile('file.txt').should('not.exist')
|
||||
|
||||
// see filter is active
|
||||
filesFilters.activeFilters().contains(/Folder/).should('be.visible')
|
||||
|
||||
// go to that folder
|
||||
navigateToFolder('folder')
|
||||
|
||||
// see filter is still active
|
||||
filesFilters.activeFilters().contains(/Folder/).should('be.visible')
|
||||
|
||||
// see that the folder is filtered
|
||||
getRowForFile('text.txt').should('not.exist')
|
||||
})
|
||||
|
||||
it('resets filter when changing the view', () => {
|
||||
// All are visible by default
|
||||
getRowForFile('folder').should('be.visible')
|
||||
getRowForFile('file.txt').should('be.visible')
|
||||
|
||||
// Set up a search query
|
||||
appNavigation.searchInput()
|
||||
.type('folder')
|
||||
|
||||
// See that only the folder is visible
|
||||
getRowForFile('folder').should('be.visible')
|
||||
getRowForFile('file.txt').should('not.exist')
|
||||
|
||||
// go to other view
|
||||
appNavigation.views()
|
||||
.findByRole('link', { name: /Personal Files/i })
|
||||
.click()
|
||||
// wait for view changed
|
||||
cy.url().should('match', /apps\/files\/personal/)
|
||||
|
||||
// see that the folder is not filtered
|
||||
getRowForFile('folder').should('be.visible')
|
||||
getRowForFile('file.txt').should('be.visible')
|
||||
|
||||
// see the filter bar is gone
|
||||
appNavigation.searchInput().should('have.value', '')
|
||||
})
|
||||
})
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
/**
|
||||
* 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 } from './FilesUtils'
|
||||
import { UnifiedSearchPage } from '../../pages/UnifiedSearch.ts'
|
||||
|
||||
describe('files: Search and filter in files list', { testIsolation: true }, () => {
|
||||
const unifiedSearch = new UnifiedSearchPage()
|
||||
let user: User
|
||||
|
||||
beforeEach(() => cy.createRandomUser().then(($user) => {
|
||||
user = $user
|
||||
|
||||
cy.mkdir(user, '/a folder')
|
||||
cy.uploadContent(user, new Blob([]), 'text/plain', '/b file')
|
||||
cy.uploadContent(user, new Blob([]), 'text/plain', '/a folder/c file')
|
||||
cy.login(user)
|
||||
cy.visit('/apps/files')
|
||||
}))
|
||||
|
||||
it('files app supports local search', () => {
|
||||
unifiedSearch.openLocalSearch()
|
||||
unifiedSearch.localSearchInput()
|
||||
.should('not.have.css', 'display', 'none')
|
||||
.and('not.be.disabled')
|
||||
})
|
||||
|
||||
it('filters current view', () => {
|
||||
// All are visible by default
|
||||
getRowForFile('a folder').should('be.visible')
|
||||
getRowForFile('b file').should('be.visible')
|
||||
|
||||
// Set up a search query
|
||||
unifiedSearch.openLocalSearch()
|
||||
unifiedSearch.typeLocalSearch('a folder')
|
||||
|
||||
// See that only the folder is visible
|
||||
getRowForFile('a folder').should('be.visible')
|
||||
getRowForFile('b file').should('not.exist')
|
||||
})
|
||||
|
||||
it('resets filter when changeing the directory', () => {
|
||||
// All are visible by default
|
||||
getRowForFile('a folder').should('be.visible')
|
||||
getRowForFile('b file').should('be.visible')
|
||||
|
||||
// Set up a search query
|
||||
unifiedSearch.openLocalSearch()
|
||||
unifiedSearch.typeLocalSearch('a folder')
|
||||
|
||||
// See that only the folder is visible
|
||||
getRowForFile('a folder').should('be.visible')
|
||||
getRowForFile('b file').should('not.exist')
|
||||
|
||||
// go to that folder
|
||||
navigateToFolder('a folder')
|
||||
|
||||
// see that the folder is not filtered
|
||||
getRowForFile('c file').should('be.visible')
|
||||
})
|
||||
|
||||
it('resets filter when changeing the view', () => {
|
||||
// All are visible by default
|
||||
getRowForFile('a folder').should('be.visible')
|
||||
getRowForFile('b file').should('be.visible')
|
||||
|
||||
// Set up a search query
|
||||
unifiedSearch.openLocalSearch()
|
||||
unifiedSearch.typeLocalSearch('a folder')
|
||||
|
||||
// See that only the folder is visible
|
||||
getRowForFile('a folder').should('be.visible')
|
||||
getRowForFile('b file').should('not.exist')
|
||||
|
||||
// go to other view
|
||||
cy.get('[data-cy-files-navigation-item="personal"] a').click({ force: true })
|
||||
// wait for view changed
|
||||
cy.url().should('match', /apps\/files\/personal/)
|
||||
|
||||
// see that the folder is not filtered
|
||||
getRowForFile('a folder').should('be.visible')
|
||||
getRowForFile('b file').should('be.visible')
|
||||
|
||||
// see the filter bar is gone
|
||||
unifiedSearch.localSearchInput().should('not.exist')
|
||||
})
|
||||
})
|
||||
34
cypress/pages/FilesFilters.ts
Normal file
34
cypress/pages/FilesFilters.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Page object model for the files filters
|
||||
*/
|
||||
export class FilesFilterPage {
|
||||
|
||||
filterContainter() {
|
||||
return cy.get('[data-cy-files-filters]')
|
||||
}
|
||||
|
||||
activeFiltersList() {
|
||||
return cy.findByRole('list', { name: 'Active filters' })
|
||||
}
|
||||
|
||||
activeFilters() {
|
||||
return this.activeFiltersList().findAllByRole('listitem')
|
||||
}
|
||||
|
||||
removeFilter(name: string | RegExp) {
|
||||
const el = typeof name === 'string'
|
||||
? this.activeFilters().should('contain.text', name)
|
||||
: this.activeFilters().should('match', name)
|
||||
el.should('exist')
|
||||
// click the button
|
||||
el.findByRole('button', { name: 'Remove filter' })
|
||||
.should('exist')
|
||||
.click({ force: true })
|
||||
}
|
||||
|
||||
}
|
||||
35
cypress/pages/FilesNavigation.ts
Normal file
35
cypress/pages/FilesNavigation.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Page object model for the files app navigation
|
||||
*/
|
||||
export class FilesNavigationPage {
|
||||
|
||||
navigation() {
|
||||
return cy.findByRole('navigation', { name: 'Files' })
|
||||
}
|
||||
|
||||
searchInput() {
|
||||
return this.navigation().findByRole('searchbox', { name: /filter filenames/i })
|
||||
}
|
||||
|
||||
searchClearButton() {
|
||||
return this.navigation().findByRole('button', { name: /clear search/i })
|
||||
}
|
||||
|
||||
settingsToggle() {
|
||||
return this.navigation().findByRole('link', { name: 'Files settings' })
|
||||
}
|
||||
|
||||
views() {
|
||||
return this.navigation().findByRole('list', { name: 'Views' })
|
||||
}
|
||||
|
||||
quota() {
|
||||
return this.navigation().find('[data-cy-files-navigation-settings-quota]')
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue