test: Add cypress tests for file list filtering

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
pull/45708/head
Ferdinand Thiessen 1 year ago
parent 1fc6b79f7c
commit 2c0e2cc09e
No known key found for this signature in database
GPG Key ID: 45FAE7268762B400
  1. 5
      apps/files/src/components/FileListFilters.vue
  2. 3
      apps/files/src/components/NavigationQuota.vue
  3. 67
      apps/files/src/views/Navigation.vue
  4. 231
      cypress/e2e/files/files-filtering.cy.ts
  5. 90
      cypress/e2e/files/files-searching.cy.ts
  6. 34
      cypress/pages/FilesFilters.ts
  7. 35
      cypress/pages/FilesNavigation.ts

@ -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,

@ -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')
})
})

@ -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 })
}
}

@ -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…
Cancel
Save