]> source.dussan.org Git - nextcloud-server.git/commitdiff
test: Add cypress tests for file list filtering
authorFerdinand Thiessen <opensource@fthiessen.de>
Thu, 25 Jul 2024 17:14:02 +0000 (19:14 +0200)
committerFerdinand Thiessen <opensource@fthiessen.de>
Thu, 25 Jul 2024 17:33:28 +0000 (19:33 +0200)
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
apps/files/src/components/FileListFilters.vue
apps/files/src/components/NavigationQuota.vue
apps/files/src/views/Navigation.vue
cypress/e2e/files/files-filtering.cy.ts [new file with mode: 0644]
cypress/e2e/files/files-searching.cy.ts [deleted file]
cypress/pages/FilesFilters.ts [new file with mode: 0644]
cypress/pages/FilesNavigation.ts [new file with mode: 0644]

index 20c9179bd15742c651cf1b7ea2a4900a440f67ec..5cdc4e877fd33a57c4e2b3e57c4bb3a9ca8e2f36 100644 (file)
@@ -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>
index 557fb240797a0518457be01a00a9c94f52f1a671..0619f6bc3fd2adff358d480e1496bfa313af6d46 100644 (file)
@@ -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>
index 51430dd54b272184f199595022d48fb3b38a44be..cfd170bd073e87a29159fdac55f8eef253cf8c72 100644 (file)
@@ -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 -->
                                <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 (file)
index 0000000..c7b147d
--- /dev/null
@@ -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 (file)
index 10ca1b4..0000000
+++ /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 (file)
index 0000000..036530f
--- /dev/null
@@ -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 (file)
index 0000000..cb3897a
--- /dev/null
@@ -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]')
+       }
+
+}