You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

217 lines
6.9 KiB

  1. /*!
  2. * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
  3. * SPDX-License-Identifier: AGPL-3.0-or-later
  4. */
  5. import type { User } from '@nextcloud/cypress'
  6. import { FilesNavigationPage } from '../../pages/FilesNavigation'
  7. import { getRowForFile, navigateToFolder } from './FilesUtils'
  8. describe('files: search', () => {
  9. let user: User
  10. const navigation = new FilesNavigationPage()
  11. before(() => {
  12. cy.createRandomUser().then(($user) => {
  13. user = $user
  14. cy.mkdir(user, '/some folder')
  15. cy.mkdir(user, '/some folder/nested folder')
  16. cy.mkdir(user, '/other folder')
  17. cy.mkdir(user, '/12345')
  18. cy.uploadContent(user, new Blob(['content']), 'text/plain', '/file.txt')
  19. cy.uploadContent(user, new Blob(['content']), 'text/plain', '/some folder/a file.txt')
  20. cy.uploadContent(user, new Blob(['content']), 'text/plain', '/some folder/a second file.txt')
  21. cy.uploadContent(user, new Blob(['content']), 'text/plain', '/some folder/nested folder/deep file.txt')
  22. cy.uploadContent(user, new Blob(['content']), 'text/plain', '/other folder/another file.txt')
  23. cy.login(user)
  24. })
  25. })
  26. beforeEach(() => {
  27. cy.visit('/apps/files')
  28. })
  29. it('updates the query on the URL', () => {
  30. navigation.searchScopeTrigger().click()
  31. navigation.searchScopeMenu()
  32. .should('be.visible')
  33. .findByRole('menuitem', { name: /search everywhere/i })
  34. .should('be.visible')
  35. .click()
  36. navigation.searchInput().type('file')
  37. cy.url().should('match', /query=file($|&)/)
  38. })
  39. it('can search globally', () => {
  40. navigation.searchScopeTrigger().click()
  41. navigation.searchScopeMenu()
  42. .should('be.visible')
  43. .findByRole('menuitem', { name: /search everywhere/i })
  44. .should('be.visible')
  45. .click()
  46. navigation.searchInput().type('file')
  47. getRowForFile('file.txt').should('be.visible')
  48. getRowForFile('a file.txt').should('be.visible')
  49. getRowForFile('a second file.txt').should('be.visible')
  50. getRowForFile('another file.txt').should('be.visible')
  51. })
  52. it('filter does also search locally', () => {
  53. navigateToFolder('some folder')
  54. getRowForFile('a file.txt').should('be.visible')
  55. navigation.searchInput().type('file')
  56. getRowForFile('a file.txt').should('be.visible')
  57. getRowForFile('a second file.txt').should('be.visible')
  58. getRowForFile('deep file.txt').should('be.visible')
  59. cy.get('[data-cy-files-list-row-fileid]').should('have.length', 3)
  60. })
  61. it('See "search everywhere" button', () => {
  62. // Not visible initially
  63. cy.get('[data-cy-files-filters]')
  64. .findByRole('button', { name: /Search everywhere/i })
  65. .should('not.to.exist')
  66. // add a filter
  67. navigation.searchInput().type('file')
  68. // see its visible
  69. cy.get('[data-cy-files-filters]')
  70. .findByRole('button', { name: /Search everywhere/i })
  71. .should('be.visible')
  72. // clear the filter
  73. navigation.searchClearButton().click()
  74. // see its not visible again
  75. cy.get('[data-cy-files-filters]')
  76. .findByRole('button', { name: /Search everywhere/i })
  77. .should('not.to.exist')
  78. })
  79. it('can make local search a global search', () => {
  80. navigateToFolder('some folder')
  81. getRowForFile('a file.txt').should('be.visible')
  82. navigation.searchInput().type('file')
  83. // see local results
  84. getRowForFile('a file.txt').should('be.visible')
  85. getRowForFile('a second file.txt').should('be.visible')
  86. getRowForFile('deep file.txt').should('be.visible')
  87. cy.get('[data-cy-files-list-row-fileid]').should('have.length', 3)
  88. // toggle global search
  89. cy.get('[data-cy-files-filters]')
  90. .findByRole('button', { name: /Search everywhere/i })
  91. .should('be.visible')
  92. .click()
  93. // see global results
  94. getRowForFile('file.txt').should('be.visible')
  95. getRowForFile('a file.txt').should('be.visible')
  96. getRowForFile('deep file.txt').should('be.visible')
  97. getRowForFile('a second file.txt').should('be.visible')
  98. getRowForFile('another file.txt').should('be.visible')
  99. })
  100. it('shows empty content when there are no results', () => {
  101. navigateToFolder('some folder')
  102. getRowForFile('a file.txt').should('be.visible')
  103. navigation.searchScopeTrigger().click()
  104. navigation.searchScopeMenu()
  105. .should('be.visible')
  106. .findByRole('menuitem', { name: /search everywhere/i })
  107. .should('be.visible')
  108. .click()
  109. navigation.searchInput().type('xyz')
  110. // see the empty content message
  111. cy.contains('[role="note"]', /No search results for .xyz./)
  112. .should('be.visible')
  113. .within(() => {
  114. // see within there is a search box with the same value
  115. cy.findByRole('searchbox', { name: /search for files/i })
  116. .should('be.visible')
  117. .and('have.value', 'xyz')
  118. })
  119. })
  120. it('can alter search', () => {
  121. navigation.searchScopeTrigger().click()
  122. navigation.searchScopeMenu()
  123. .should('be.visible')
  124. .findByRole('menuitem', { name: /search everywhere/i })
  125. .should('be.visible')
  126. .click()
  127. navigation.searchInput().type('other')
  128. getRowForFile('another file.txt').should('be.visible')
  129. getRowForFile('other folder').should('be.visible')
  130. cy.get('[data-cy-files-list-row-fileid]').should('have.length', 2)
  131. navigation.searchInput().type(' file')
  132. navigation.searchInput().should('have.value', 'other file')
  133. getRowForFile('another file.txt').should('be.visible')
  134. cy.get('[data-cy-files-list-row-fileid]').should('have.length', 1)
  135. })
  136. it('returns to file list if search is cleared', () => {
  137. navigation.searchScopeTrigger().click()
  138. navigation.searchScopeMenu()
  139. .should('be.visible')
  140. .findByRole('menuitem', { name: /search everywhere/i })
  141. .should('be.visible')
  142. .click()
  143. navigation.searchInput().type('other')
  144. getRowForFile('another file.txt').should('be.visible')
  145. getRowForFile('other folder').should('be.visible')
  146. cy.get('[data-cy-files-list-row-fileid]').should('have.length', 2)
  147. navigation.searchClearButton().click()
  148. navigation.searchInput().should('have.value', '')
  149. getRowForFile('file.txt').should('be.visible')
  150. cy.get('[data-cy-files-list-row-fileid]').should('have.length', 5)
  151. })
  152. /**
  153. * Problem:
  154. * 1. Being on the search view
  155. * 2. Press the refresh button (name of the current view)
  156. * 3. See that the router link does not preserve the query
  157. *
  158. * We fix this with a navigation guard and need to verify that it works
  159. */
  160. it('keeps the query in the URL', () => {
  161. navigation.searchScopeTrigger().click()
  162. navigation.searchScopeMenu()
  163. .should('be.visible')
  164. .findByRole('menuitem', { name: /search everywhere/i })
  165. .should('be.visible')
  166. .click()
  167. navigation.searchInput().type('file')
  168. // see that the search view is loaded
  169. getRowForFile('a file.txt').should('be.visible')
  170. // see the correct url
  171. cy.url().should('match', /query=file($|&)/)
  172. cy.intercept('SEARCH', '**/remote.php/dav/').as('search')
  173. // refresh the view
  174. cy.findByRole('button', { description: /reload current directory/i }).click()
  175. // wait for the request
  176. cy.wait('@search')
  177. // see that the search view is reloaded
  178. getRowForFile('a file.txt').should('be.visible')
  179. // see the correct url
  180. cy.url().should('match', /query=file($|&)/)
  181. })
  182. })