test_search.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. from parameterized import parameterized
  2. from base import BaseTestCase, Search
  3. # [url, expected active tab label]
  4. active_tabs = [
  5. ['search?f=tweets&q=nasa', 'Latest'],
  6. ['search?f=top&q=nasa', 'Top'],
  7. ['search?f=media&q=nasa', 'Media'],
  8. ['search?f=users&q=nasa', 'Users'],
  9. ['search?f=lists&q=test', 'Lists'],
  10. # unknown/hostile values fall back to Latest
  11. ['search?f=garbage&q=nasa', 'Latest'],
  12. ['search?f=%3Cscript%3E&q=nasa', 'Latest'],
  13. # x.com URL compat: f=live/user/list (f=media/top match natively)
  14. ['search?f=live&q=nasa', 'Latest'],
  15. ['search?f=user&q=nasa', 'Users'],
  16. ['search?f=list&q=test', 'Lists'],
  17. ]
  18. results_pages = [
  19. ['search?f=tweets&q=nasa'],
  20. ['search?f=top&q=nasa'],
  21. ['search?f=media&q=nasa'],
  22. ]
  23. class SearchProductTest(BaseTestCase):
  24. @parameterized.expand(active_tabs)
  25. def test_active_tab(self, page, expected_active):
  26. self.open_nitter(page)
  27. active = self.get_text(Search.tab_active)
  28. self.assert_equal(active.strip(), expected_active)
  29. def test_all_tabs_present(self):
  30. self.open_nitter('search?f=tweets&q=nasa')
  31. tabs = self.find_elements(Search.tab_item)
  32. labels = [t.text.strip() for t in tabs]
  33. self.assert_equal(labels, ['Top', 'Latest', 'Media', 'Users', 'Lists'])
  34. @parameterized.expand(results_pages)
  35. def test_results_render(self, page):
  36. self.open_nitter(page)
  37. self.assert_element('.timeline .timeline-item')
  38. def test_tab_links_carry_kind(self):
  39. self.open_nitter('search?f=tweets&q=nasa')
  40. self.assert_element('.tab-item a[href="?f=top&q=nasa"]')
  41. self.assert_element('.tab-item a[href="?f=media&q=nasa"]')
  42. self.assert_element('.tab-item a[href="?f=tweets&q=nasa"]')
  43. self.assert_element('.tab-item a[href="?f=users&q=nasa"]')
  44. self.assert_element('.tab-item a[href="?f=lists&q=nasa"]')
  45. def test_show_more_preserves_kind(self):
  46. self.open_nitter('search?f=media&q=nasa')
  47. href = self.get_attribute('.show-more a', 'href')
  48. self.assert_true('f=media' in href, f'f=media missing from: {href}')
  49. def test_search_form_preserves_kind(self):
  50. self.open_nitter('search?f=top&q=nasa')
  51. self.assert_element_present('.search-field input[name="f"][value="top"]')
  52. def test_media_operators_compose(self):
  53. self.open_nitter('search?f=media&q=nasa&e-nativeretweets=on')
  54. self.assert_element('.timeline .timeline-item')
  55. @parameterized.expand([['DAAC'], ['AB'], ['maxid:'], ['maxid:abc']])
  56. def test_garbage_cursor_no_crash(self, cursor):
  57. # short/invalid cursors must render the page, not a 500 error
  58. self.open_nitter(f'search?f=media&q=nasa&cursor={cursor}')
  59. self.assert_element(Search.tab_active)
  60. def test_no_results(self):
  61. self.open_nitter('search?f=media&q=xkqzjwv_no_results_2026')
  62. self.assert_text('No items found', '.timeline-none')
  63. def test_list_results_render(self):
  64. self.open_nitter('search?f=lists&q=test')
  65. self.assert_element('.timeline-item.list-result')
  66. self.assert_element('.list-result .list-name')
  67. self.assert_element('.list-result .list-members')
  68. def test_list_card_links_to_list(self):
  69. self.open_nitter('search?f=lists&q=test')
  70. href = self.get_attribute('.list-result .list-name', 'href')
  71. self.assert_true('/i/lists/' in href, f'unexpected list link: {href}')
  72. def test_list_row_clickable(self):
  73. self.open_nitter('search?f=lists&q=test')
  74. href = self.get_attribute('.list-result a.tweet-link', 'href')
  75. self.assert_true('/i/lists/' in href, f'unexpected row link: {href}')
  76. def test_list_avatar_links_to_user(self):
  77. self.open_nitter('search?f=lists&q=test')
  78. # the avatar link in a row must point at the user named in that row
  79. row = '.list-result:has(a.facepile-link)'
  80. self.assert_element(f'{row} a.facepile-link > img')
  81. href = self.get_attribute(f'{row} a.facepile-link', 'href')
  82. ctx = self.get_text(f'{row} .list-result-context')
  83. mentioned = ctx.split('@')[-1].strip()
  84. self.assert_true(href.endswith('/' + mentioned),
  85. f'avatar link {href} does not match @{mentioned}')
  86. def test_list_pagination_preserves_kind(self):
  87. self.open_nitter('search?f=lists&q=test')
  88. href = self.get_attribute('.show-more a', 'href')
  89. self.assert_true('f=lists' in href, f'f=lists missing from: {href}')
  90. def test_list_garbage_cursor_no_crash(self):
  91. self.open_nitter('search?f=lists&q=test&cursor=DAAC')
  92. self.assert_element(Search.tab_active)
  93. def test_media_view_tabs_present(self):
  94. self.open_nitter('search?f=media&q=nasa')
  95. tabs = self.find_elements('.media-view-tabs .tab-item')
  96. labels = [t.text.strip() for t in tabs]
  97. self.assert_equal(labels, ['Timeline', 'Grid', 'Gallery'])
  98. def test_media_view_grid(self):
  99. self.open_nitter('search?f=media&q=nasa&view=grid')
  100. self.assert_element('.timeline.media-grid-view')
  101. def test_media_view_gallery(self):
  102. self.open_nitter('search?f=media&q=nasa&view=gallery')
  103. self.assert_element('.timeline.media-gallery-view .gallery-masonry')
  104. def test_media_view_tabs_only_on_media(self):
  105. self.open_nitter('search?f=tweets&q=nasa')
  106. self.assert_element_not_present('.media-view-tabs')