aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/files/src/components/FileListFilters.vue5
-rw-r--r--apps/files/src/components/NavigationQuota.vue3
-rw-r--r--apps/files/src/views/Navigation.vue67
-rw-r--r--cypress/e2e/files/files-filtering.cy.ts231
-rw-r--r--cypress/e2e/files/files-searching.cy.ts90
-rw-r--r--cypress/pages/FilesFilters.ts34
-rw-r--r--cypress/pages/FilesNavigation.ts35
7 files changed, 340 insertions, 125 deletions
diff --git a/apps/files/src/components/FileListFilters.vue b/apps/files/src/components/FileListFilters.vue
index 20c9179bd15..5cdc4e877fd 100644
--- a/apps/files/src/components/FileListFilters.vue
+++ b/apps/files/src/components/FileListFilters.vue
@@ -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>
diff --git a/apps/files/src/components/NavigationQuota.vue b/apps/files/src/components/NavigationQuota.vue
index 557fb240797..0619f6bc3fd 100644
--- a/apps/files/src/components/NavigationQuota.vue
+++ b/apps/files/src/components/NavigationQuota.vue
@@ -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>
diff --git a/apps/files/src/views/Navigation.vue b/apps/files/src/views/Navigation.vue
index 51430dd54b2..cfd170bd073 100644
--- a/apps/files/src/views/Navigation.vue
+++ b/apps/files/src/views/Navigation.vue
@@ -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,
diff --git a/cypress/e2e/files/files-filtering.cy.ts b/cypress/e2e/files/files-filtering.cy.ts
new file mode 100644
index 00000000000..c7b147d4a9c
--- /dev/null
+++ b/cypress/e2e/files/files-filtering.cy.ts
@@ -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', '')
+ })
+})
diff --git a/cypress/e2e/files/files-searching.cy.ts b/cypress/e2e/files/files-searching.cy.ts
deleted file mode 100644
index 10ca1b44e2f..00000000000
--- a/cypress/e2e/files/files-searching.cy.ts
+++ /dev/null
@@ -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')
- })
-})
diff --git a/cypress/pages/FilesFilters.ts b/cypress/pages/FilesFilters.ts
new file mode 100644
index 00000000000..036530f1e8a
--- /dev/null
+++ b/cypress/pages/FilesFilters.ts
@@ -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 })
+ }
+
+}
diff --git a/cypress/pages/FilesNavigation.ts b/cypress/pages/FilesNavigation.ts
new file mode 100644
index 00000000000..cb3897afb90
--- /dev/null
+++ b/cypress/pages/FilesNavigation.ts
@@ -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]')
+ }
+
+}