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.

document-detail-navigation-fix.test.tsx 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. /**
  2. * Document Detail Navigation Fix Verification Test
  3. *
  4. * This test specifically validates that the backToPrev function in the document detail
  5. * component correctly preserves pagination and filter states.
  6. */
  7. import { fireEvent, render, screen } from '@testing-library/react'
  8. import { useRouter } from 'next/navigation'
  9. import { useDocumentDetail, useDocumentMetadata } from '@/service/knowledge/use-document'
  10. // Mock Next.js router
  11. const mockPush = jest.fn()
  12. jest.mock('next/navigation', () => ({
  13. useRouter: jest.fn(() => ({
  14. push: mockPush,
  15. })),
  16. }))
  17. // Mock the document service hooks
  18. jest.mock('@/service/knowledge/use-document', () => ({
  19. useDocumentDetail: jest.fn(),
  20. useDocumentMetadata: jest.fn(),
  21. useInvalidDocumentList: jest.fn(() => jest.fn()),
  22. }))
  23. // Mock other dependencies
  24. jest.mock('@/context/dataset-detail', () => ({
  25. useDatasetDetailContext: jest.fn(() => [null]),
  26. }))
  27. jest.mock('@/service/use-base', () => ({
  28. useInvalid: jest.fn(() => jest.fn()),
  29. }))
  30. jest.mock('@/service/knowledge/use-segment', () => ({
  31. useSegmentListKey: jest.fn(),
  32. useChildSegmentListKey: jest.fn(),
  33. }))
  34. // Create a minimal version of the DocumentDetail component that includes our fix
  35. const DocumentDetailWithFix = ({ datasetId, documentId }: { datasetId: string; documentId: string }) => {
  36. const router = useRouter()
  37. // This is the FIXED implementation from detail/index.tsx
  38. const backToPrev = () => {
  39. // Preserve pagination and filter states when navigating back
  40. const searchParams = new URLSearchParams(window.location.search)
  41. const queryString = searchParams.toString()
  42. const separator = queryString ? '?' : ''
  43. const backPath = `/datasets/${datasetId}/documents${separator}${queryString}`
  44. router.push(backPath)
  45. }
  46. return (
  47. <div data-testid="document-detail-fixed">
  48. <button data-testid="back-button-fixed" onClick={backToPrev}>
  49. Back to Documents
  50. </button>
  51. <div data-testid="document-info">
  52. Dataset: {datasetId}, Document: {documentId}
  53. </div>
  54. </div>
  55. )
  56. }
  57. describe('Document Detail Navigation Fix Verification', () => {
  58. beforeEach(() => {
  59. jest.clearAllMocks()
  60. // Mock successful API responses
  61. ;(useDocumentDetail as jest.Mock).mockReturnValue({
  62. data: {
  63. id: 'doc-123',
  64. name: 'Test Document',
  65. display_status: 'available',
  66. enabled: true,
  67. archived: false,
  68. },
  69. error: null,
  70. })
  71. ;(useDocumentMetadata as jest.Mock).mockReturnValue({
  72. data: null,
  73. error: null,
  74. })
  75. })
  76. describe('Query Parameter Preservation', () => {
  77. test('preserves pagination state (page 3, limit 25)', () => {
  78. // Simulate user coming from page 3 with 25 items per page
  79. Object.defineProperty(window, 'location', {
  80. value: {
  81. search: '?page=3&limit=25',
  82. },
  83. writable: true,
  84. })
  85. render(<DocumentDetailWithFix datasetId="dataset-123" documentId="doc-456" />)
  86. // User clicks back button
  87. fireEvent.click(screen.getByTestId('back-button-fixed'))
  88. // Should preserve the pagination state
  89. expect(mockPush).toHaveBeenCalledWith('/datasets/dataset-123/documents?page=3&limit=25')
  90. console.log('✅ Pagination state preserved: page=3&limit=25')
  91. })
  92. test('preserves search keyword and filters', () => {
  93. // Simulate user with search and filters applied
  94. Object.defineProperty(window, 'location', {
  95. value: {
  96. search: '?page=2&limit=10&keyword=API%20documentation&status=active',
  97. },
  98. writable: true,
  99. })
  100. render(<DocumentDetailWithFix datasetId="dataset-123" documentId="doc-456" />)
  101. fireEvent.click(screen.getByTestId('back-button-fixed'))
  102. // Should preserve all query parameters
  103. expect(mockPush).toHaveBeenCalledWith('/datasets/dataset-123/documents?page=2&limit=10&keyword=API+documentation&status=active')
  104. console.log('✅ Search and filters preserved')
  105. })
  106. test('handles complex query parameters with special characters', () => {
  107. // Test with complex query string including encoded characters
  108. Object.defineProperty(window, 'location', {
  109. value: {
  110. search: '?page=1&limit=50&keyword=test%20%26%20debug&sort=name&order=desc&filter=%7B%22type%22%3A%22pdf%22%7D',
  111. },
  112. writable: true,
  113. })
  114. render(<DocumentDetailWithFix datasetId="dataset-123" documentId="doc-456" />)
  115. fireEvent.click(screen.getByTestId('back-button-fixed'))
  116. // URLSearchParams will normalize the encoding, but preserve all parameters
  117. const expectedCall = mockPush.mock.calls[0][0]
  118. expect(expectedCall).toMatch(/^\/datasets\/dataset-123\/documents\?/)
  119. expect(expectedCall).toMatch(/page=1/)
  120. expect(expectedCall).toMatch(/limit=50/)
  121. expect(expectedCall).toMatch(/keyword=test/)
  122. expect(expectedCall).toMatch(/sort=name/)
  123. expect(expectedCall).toMatch(/order=desc/)
  124. console.log('✅ Complex query parameters handled:', expectedCall)
  125. })
  126. test('handles empty query parameters gracefully', () => {
  127. // No query parameters in URL
  128. Object.defineProperty(window, 'location', {
  129. value: {
  130. search: '',
  131. },
  132. writable: true,
  133. })
  134. render(<DocumentDetailWithFix datasetId="dataset-123" documentId="doc-456" />)
  135. fireEvent.click(screen.getByTestId('back-button-fixed'))
  136. // Should navigate to clean documents URL
  137. expect(mockPush).toHaveBeenCalledWith('/datasets/dataset-123/documents')
  138. console.log('✅ Empty parameters handled gracefully')
  139. })
  140. })
  141. describe('Different Dataset IDs', () => {
  142. test('works with different dataset identifiers', () => {
  143. Object.defineProperty(window, 'location', {
  144. value: {
  145. search: '?page=5&limit=10',
  146. },
  147. writable: true,
  148. })
  149. // Test with different dataset ID format
  150. render(<DocumentDetailWithFix datasetId="ds-prod-2024-001" documentId="doc-456" />)
  151. fireEvent.click(screen.getByTestId('back-button-fixed'))
  152. expect(mockPush).toHaveBeenCalledWith('/datasets/ds-prod-2024-001/documents?page=5&limit=10')
  153. console.log('✅ Works with different dataset ID formats')
  154. })
  155. })
  156. describe('Real User Scenarios', () => {
  157. test('scenario: user searches, goes to page 3, views document, clicks back', () => {
  158. // User searched for "API" and navigated to page 3
  159. Object.defineProperty(window, 'location', {
  160. value: {
  161. search: '?keyword=API&page=3&limit=10',
  162. },
  163. writable: true,
  164. })
  165. render(<DocumentDetailWithFix datasetId="main-dataset" documentId="api-doc-123" />)
  166. // User decides to go back to continue browsing
  167. fireEvent.click(screen.getByTestId('back-button-fixed'))
  168. // Should return to page 3 of API search results
  169. expect(mockPush).toHaveBeenCalledWith('/datasets/main-dataset/documents?keyword=API&page=3&limit=10')
  170. console.log('✅ Real user scenario: search + pagination preserved')
  171. })
  172. test('scenario: user applies multiple filters, goes to document, returns', () => {
  173. // User has applied multiple filters and is on page 2
  174. Object.defineProperty(window, 'location', {
  175. value: {
  176. search: '?page=2&limit=25&status=active&type=pdf&sort=created_at&order=desc',
  177. },
  178. writable: true,
  179. })
  180. render(<DocumentDetailWithFix datasetId="filtered-dataset" documentId="filtered-doc" />)
  181. fireEvent.click(screen.getByTestId('back-button-fixed'))
  182. // All filters should be preserved
  183. expect(mockPush).toHaveBeenCalledWith('/datasets/filtered-dataset/documents?page=2&limit=25&status=active&type=pdf&sort=created_at&order=desc')
  184. console.log('✅ Complex filtering scenario preserved')
  185. })
  186. })
  187. describe('Error Handling and Edge Cases', () => {
  188. test('handles malformed query parameters gracefully', () => {
  189. // Test with potentially problematic query string
  190. Object.defineProperty(window, 'location', {
  191. value: {
  192. search: '?page=invalid&limit=&keyword=test&=emptykey&malformed',
  193. },
  194. writable: true,
  195. })
  196. render(<DocumentDetailWithFix datasetId="dataset-123" documentId="doc-456" />)
  197. // Should not throw errors
  198. expect(() => {
  199. fireEvent.click(screen.getByTestId('back-button-fixed'))
  200. }).not.toThrow()
  201. // Should still attempt navigation (URLSearchParams will clean up the parameters)
  202. expect(mockPush).toHaveBeenCalled()
  203. const navigationPath = mockPush.mock.calls[0][0]
  204. expect(navigationPath).toMatch(/^\/datasets\/dataset-123\/documents/)
  205. console.log('✅ Malformed parameters handled gracefully:', navigationPath)
  206. })
  207. test('handles very long query strings', () => {
  208. // Test with a very long query string
  209. const longKeyword = 'a'.repeat(1000)
  210. Object.defineProperty(window, 'location', {
  211. value: {
  212. search: `?page=1&keyword=${longKeyword}`,
  213. },
  214. writable: true,
  215. })
  216. render(<DocumentDetailWithFix datasetId="dataset-123" documentId="doc-456" />)
  217. expect(() => {
  218. fireEvent.click(screen.getByTestId('back-button-fixed'))
  219. }).not.toThrow()
  220. expect(mockPush).toHaveBeenCalled()
  221. console.log('✅ Long query strings handled')
  222. })
  223. })
  224. describe('Performance Verification', () => {
  225. test('navigation function executes quickly', () => {
  226. Object.defineProperty(window, 'location', {
  227. value: {
  228. search: '?page=1&limit=10&keyword=test',
  229. },
  230. writable: true,
  231. })
  232. render(<DocumentDetailWithFix datasetId="dataset-123" documentId="doc-456" />)
  233. const startTime = performance.now()
  234. fireEvent.click(screen.getByTestId('back-button-fixed'))
  235. const endTime = performance.now()
  236. const executionTime = endTime - startTime
  237. // Should execute in less than 10ms
  238. expect(executionTime).toBeLessThan(10)
  239. console.log(`⚡ Navigation execution time: ${executionTime.toFixed(2)}ms`)
  240. })
  241. })
  242. })