search_spec.rb 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. # Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe 'Search', authenticated: true, searchindex: true, type: :system do
  4. let(:group_1) { create(:group) }
  5. let(:group_2) { create(:group) }
  6. let(:macro_without_group) { create(:macro) }
  7. let(:macro_note) { create(:macro, name: 'Macro note', perform: { 'article.note'=>{ 'body' => 'macro body', 'internal' => 'true', 'subject' => 'macro note' } }) }
  8. let(:macro_group1) { create(:macro, groups: [group_1]) }
  9. let(:macro_group2) { create(:macro, groups: [group_2]) }
  10. let!(:ticket_1) { create(:ticket, title: 'Testing Ticket 1', group: group_1) }
  11. let!(:ticket_2) { create(:ticket, title: 'Testing Ticket 2', group: group_2) }
  12. let(:note) { 'Test note' }
  13. before do
  14. ticket_1 && ticket_2
  15. searchindex_model_reload([Ticket, Organization, User])
  16. visit '/'
  17. end
  18. it 'shows default widgets' do
  19. fill_in id: 'global-search', with: '"Welcome"'
  20. click_on 'Show Search Details'
  21. within '#navigation .tasks a[data-key=Search]' do
  22. expect(page).to have_content '"Welcome"'
  23. end
  24. end
  25. context 'with ticket search result', authenticated_as: :authenticate do
  26. let(:agent) { create(:agent, groups: Group.all) }
  27. def authenticate
  28. ticket_1 && ticket_2
  29. agent
  30. end
  31. before do
  32. fill_in id: 'global-search', with: 'Testing'
  33. click_on 'Show Search Details'
  34. find('[data-tab-content=Ticket]').click
  35. end
  36. context 'checkbox' do
  37. it 'has checkbox for each ticket records' do
  38. within '.detail-search table.table' do
  39. expect(page).to have_xpath(".//td[contains(@class, 'js-checkbox-field')]//input[@type='checkbox']", visible: :all, minimum: 2)
  40. end
  41. end
  42. it 'has select all checkbox' do
  43. within '.detail-search table.table' do
  44. expect(page).to have_xpath(".//th//input[@type='checkbox' and @name='bulk_all']", visible: :all, count: 1)
  45. end
  46. end
  47. it 'shows bulkform when checkbox is checked' do
  48. within '.detail-search table.table' do
  49. find("tr[data-id='#{ticket_1.id}']").check('bulk', allow_label_click: true)
  50. end
  51. expect(page).to have_selector('.bulkAction.no-sidebar')
  52. expect(page).to have_no_selector('.bulkAction.no-sidebar.hide', visible: :all)
  53. end
  54. it 'shows bulkform when all checkbox is checked' do
  55. within '.detail-search table.table' do
  56. find('th.table-checkbox').check('bulk_all', allow_label_click: true)
  57. end
  58. expect(page).to have_selector('.bulkAction.no-sidebar')
  59. expect(page).to have_no_selector('.bulkAction.no-sidebar.hide', visible: :all)
  60. end
  61. it 'hides bulkform when checkbox is unchecked' do
  62. within '.detail-search table.table' do
  63. find('th.table-checkbox').check('bulk_all', allow_label_click: true)
  64. all('.js-tableBody tr.item').each { |row| row.uncheck('bulk', allow_label_click: true) }
  65. end
  66. expect(page).to have_selector('.bulkAction.no-sidebar.hide', visible: :hide)
  67. end
  68. end
  69. context 'with bulkform activated' do
  70. before do
  71. find('th.table-checkbox').check('bulk_all', allow_label_click: true)
  72. end
  73. it 'has group label' do
  74. within '.bulkAction .bulkAction-form' do
  75. expect(page).to have_content 'GROUP'
  76. end
  77. end
  78. it 'has owner label' do
  79. within '.bulkAction .bulkAction-form' do
  80. expect(page).to have_content 'OWNER'
  81. end
  82. end
  83. it 'has state label' do
  84. within '.bulkAction .bulkAction-form' do
  85. expect(page).to have_content 'STATE'
  86. end
  87. end
  88. it 'has priority label' do
  89. within '.bulkAction .bulkAction-form' do
  90. expect(page).to have_content 'PRIORITY'
  91. end
  92. end
  93. end
  94. context 'bulk note' do
  95. it 'adds note to selected ticket' do
  96. within :active_content do
  97. find("tr[data-id='#{ticket_1.id}']").check('bulk', allow_label_click: true)
  98. click '.js-confirm'
  99. find('.js-confirm-step textarea').fill_in with: note
  100. click '.js-submit'
  101. end
  102. expect do
  103. wait.until { ticket_1.articles.last&.body == note }
  104. end.not_to raise_error
  105. end
  106. end
  107. context 'with drag and drop' do
  108. context 'when checked tickets are dragged' do
  109. it 'shows the batch actions' do
  110. within(:active_content, '.main .table') do
  111. # get element to move
  112. element = page.find(:table_row, ticket_1.id).native
  113. click_and_hold(element)
  114. # move element a bit to display batch actions
  115. move_mouse_by(0, 5)
  116. # move mouse again to trigger the event for chrome
  117. move_mouse_by(0, 7)
  118. end
  119. expect(page).to have_selector('.batch-overlay-circle--top.js-batch-macro-circle')
  120. .and(have_selector('.batch-overlay-circle--bottom.js-batch-assign-circle'))
  121. end
  122. end
  123. end
  124. end
  125. context 'with ticket search result for macros bulk action', authenticated_as: :authenticate do
  126. let(:group_3) { create(:group) }
  127. let(:search_query) { 'Testing' }
  128. let!(:ticket_3) { create(:ticket, title: 'Testing Ticket 3', group: group_3) }
  129. let(:agent) { create(:agent, groups: Group.all) }
  130. before do
  131. fill_in id: 'global-search', with: search_query
  132. click_on 'Show Search Details'
  133. find('[data-tab-content=Ticket]').click
  134. await_empty_ajax_queue
  135. end
  136. describe 'group-dependent macros' do
  137. def authenticate
  138. ticket_1 && ticket_2 && ticket_3
  139. macro_without_group && macro_group1 && macro_group2
  140. agent
  141. end
  142. it 'shows only non-group macro when ticket does not match any group macros' do
  143. within(:active_content) do
  144. display_macro_batches ticket_3
  145. expect(page).to have_selector(:macro_batch, macro_without_group.id)
  146. .and(have_no_selector(:macro_batch, macro_group1.id))
  147. .and(have_no_selector(:macro_batch, macro_group2.id))
  148. end
  149. end
  150. it 'shows non-group and matching group macros for matching ticket' do
  151. display_macro_batches ticket_1
  152. within(:active_content) do
  153. expect(page).to have_selector(:macro_batch, macro_without_group.id)
  154. .and(have_selector(:macro_batch, macro_group1.id))
  155. .and(have_no_selector(:macro_batch, macro_group2.id))
  156. end
  157. end
  158. end
  159. context 'with macro batch overlay' do
  160. shared_examples "adding 'small' class to macro element" do
  161. it 'adds a "small" class to the macro element' do
  162. display_macro_batches ticket_1
  163. within(:active_content) do
  164. expect(page).to have_selector('.batch-overlay-macro-entry.small')
  165. end
  166. end
  167. end
  168. shared_examples "not adding 'small' class to macro element" do
  169. it 'does not add a "small" class to the macro element' do
  170. display_macro_batches ticket_1
  171. within(:active_content) do
  172. expect(page).to have_no_selector('.batch-overlay-macro-entry.small')
  173. end
  174. end
  175. end
  176. shared_examples 'showing all macros' do
  177. it 'shows all macros' do
  178. display_macro_batches ticket_1
  179. within(:active_content) do
  180. expect(page).to have_selector('.batch-overlay-macro-entry', count: all)
  181. end
  182. end
  183. end
  184. shared_examples 'showing some macros' do |count|
  185. it 'shows all macros' do
  186. display_macro_batches ticket_1
  187. within(:active_content) do
  188. expect(page).to have_selector('.batch-overlay-macro-entry', count: count)
  189. end
  190. end
  191. end
  192. def authenticate
  193. ticket_1 && ticket_2
  194. Macro.destroy_all && create_list(:macro, all)
  195. agent
  196. end
  197. context 'with few macros' do
  198. let(:all) { 15 }
  199. context 'when on large screen', screen_size: :desktop do
  200. it_behaves_like 'showing all macros'
  201. it_behaves_like "not adding 'small' class to macro element"
  202. end
  203. context 'when on small screen', screen_size: :tablet do
  204. it_behaves_like 'showing all macros'
  205. it_behaves_like "not adding 'small' class to macro element"
  206. end
  207. end
  208. context 'with many macros' do
  209. let(:all) { 50 }
  210. context 'when on large screen', screen_size: :desktop do
  211. it_behaves_like 'showing some macros', 32
  212. end
  213. context 'when on small screen', screen_size: :tablet do
  214. it_behaves_like 'showing some macros', 24
  215. it_behaves_like "adding 'small' class to macro element"
  216. end
  217. end
  218. end
  219. end
  220. context 'Organization members', authenticated_as: :authenticate do
  221. let(:organization) { create(:organization) }
  222. let(:members) { organization.members.reorder(id: :asc) }
  223. def authenticate
  224. create_list(:customer, 50, organization: organization)
  225. true
  226. end
  227. before do
  228. fill_in id: 'global-search', with: organization.name.to_s
  229. end
  230. it 'shows only first 10 members' do
  231. expect(page).to have_text(organization.name)
  232. popover_on_hover(first('a.nav-tab.organization'))
  233. expect(page).to have_text(members[9].fullname, wait: 30)
  234. expect(page).to have_no_text(members[10].fullname)
  235. end
  236. end
  237. context 'inactive user and organizations' do
  238. before do
  239. create(:organization, name: 'Example Inc.', active: true)
  240. create(:organization, name: 'Example Inactive Inc.', active: false)
  241. create(:customer, firstname: 'Firstname', lastname: 'Active', active: true)
  242. create(:customer, firstname: 'Firstname', lastname: 'Inactive', active: false)
  243. searchindex_model_reload([User, Organization])
  244. end
  245. it 'check that inactive organizations are marked correctly' do
  246. fill_in id: 'global-search', with: '"Example"'
  247. expect(page).to have_css('.nav-tab--search.organization', minimum: 2)
  248. expect(page).to have_css('.nav-tab--search.organization.is-inactive', count: 1)
  249. end
  250. it 'check that inactive users are marked correctly' do
  251. fill_in id: 'global-search', with: '"Firstname"'
  252. expect(page).to have_css('.nav-tab--search.user', minimum: 2)
  253. expect(page).to have_css('.nav-tab--search.user.is-inactive', count: 1)
  254. end
  255. it 'check that inactive users are also marked in the popover for the quick search result' do
  256. fill_in id: 'global-search', with: '"Firstname"'
  257. popover_on_hover(find('.nav-tab--search.user.is-inactive'))
  258. expect(page).to have_css('.popover-title .is-inactive', count: 1)
  259. end
  260. end
  261. describe 'Search is not triggered/updated if url of search is updated new search item or new search is triggered via global search #3873', authenticated_as: :authenticate do
  262. let(:agent) { create(:agent, groups: Group.all) }
  263. def authenticate
  264. ticket_1 && ticket_2
  265. agent
  266. end
  267. context 'when search changed via input box' do
  268. before do
  269. visit '#search'
  270. end
  271. it 'does switch search results properly' do
  272. page.find('.js-search').fill_in(with: '"Testing Ticket 1"', fill_options: { clear: :backspace })
  273. expect(page.find('.js-tableBody')).to have_text('Testing Ticket 1')
  274. expect(page.find('.js-tableBody')).to have_no_text('Testing Ticket 2')
  275. expect(current_url).to include('Testing%20Ticket%201')
  276. # switch by global search
  277. page.find('.js-search').fill_in(with: '"Testing Ticket 2"', fill_options: { clear: :backspace })
  278. expect(page.find('.js-tableBody')).to have_text('Testing Ticket 2')
  279. expect(page.find('.js-tableBody')).to have_no_text('Testing Ticket 1')
  280. expect(current_url).to include('Testing%20Ticket%202')
  281. end
  282. end
  283. context 'when search changed via global search' do
  284. before do
  285. fill_in id: 'global-search', with: '"Testing Ticket 1"'
  286. click_on 'Show Search Details'
  287. end
  288. it 'does switch search results properly' do
  289. expect(page.find('.js-tableBody')).to have_text('Testing Ticket 1')
  290. expect(page.find('.js-tableBody')).to have_no_text('Testing Ticket 2')
  291. expect(current_url).to include('Testing%20Ticket%201')
  292. # switch by global search
  293. fill_in id: 'global-search', with: '"Testing Ticket 2"'
  294. click_on 'Show Search Details'
  295. expect(page.find('.js-tableBody')).to have_text('Testing Ticket 2')
  296. expect(page.find('.js-tableBody')).to have_no_text('Testing Ticket 1')
  297. expect(current_url).to include('Testing%20Ticket%202')
  298. end
  299. end
  300. context 'when search is changed via url' do
  301. before do
  302. visit '#search/"Testing Ticket 1"'
  303. end
  304. it 'does switch search results properly' do
  305. expect(page.find('.js-tableBody')).to have_text('Testing Ticket 1')
  306. expect(page.find('.js-tableBody')).to have_no_text('Testing Ticket 2')
  307. expect(current_url).to include('Testing%20Ticket%201')
  308. # switch by url
  309. visit '#search/"Testing Ticket 2"'
  310. expect(page.find('.js-tableBody')).to have_text('Testing Ticket 2')
  311. expect(page.find('.js-tableBody')).to have_no_text('Testing Ticket 1')
  312. expect(current_url).to include('Testing%20Ticket%202')
  313. end
  314. end
  315. end
  316. context 'Assign user to multiple organizations #1573', authenticated_as: :authenticate do
  317. let(:organizations) { create_list(:organization, 20) }
  318. let(:customer) { create(:customer, organization: organizations[0], organizations: organizations[1..]) }
  319. context 'when agent' do
  320. def authenticate
  321. customer
  322. true
  323. end
  324. before do
  325. fill_in id: 'global-search', with: customer.firstname.to_s
  326. end
  327. it 'shows only first 3 organizations' do
  328. expect(page).to have_text(customer.firstname)
  329. popover_on_hover(first('a.nav-tab.user'))
  330. within '.popover' do
  331. expect(page).to have_text(organizations[2].name, wait: 30)
  332. expect(page).to have_no_text(organizations[10].name)
  333. end
  334. end
  335. end
  336. context 'when customer', authenticated_as: :customer do
  337. before do
  338. fill_in id: 'global-search', with: organizations[0].name.to_s
  339. end
  340. it 'does not show any organizations in global search because only agents have access to it' do
  341. within '.global-search-result' do
  342. expect(page).to have_no_text(organizations[0].name)
  343. end
  344. end
  345. end
  346. end
  347. describe 'Searches display all groups and owners on bulk selections #4054', authenticated_as: :authenticate do
  348. let(:group_1) { create(:group) }
  349. let(:group_2) { create(:group) }
  350. let(:agent_1) { create(:agent, groups: [group_1]) }
  351. let(:agent_2) { create(:agent, groups: [group_2]) }
  352. let(:agent_all) { create(:agent, groups: [group_1, group_2]) }
  353. let!(:ticket_1) { create(:ticket, group: group_1, title: '4054 group 1') }
  354. let!(:ticket_2) { create(:ticket, group: group_2, title: '4054 group 2') }
  355. def authenticate
  356. agent_1 && agent_2 && agent_all
  357. ticket_1 && ticket_2
  358. agent_all
  359. end
  360. def check_owner_empty
  361. expect(page).to have_select('owner_id', text: '-', visible: :all)
  362. expect(page).to have_no_select('owner_id', text: agent_1.fullname, visible: :all)
  363. expect(page).to have_no_select('owner_id', text: agent_2.fullname, visible: :all)
  364. end
  365. def click_ticket(ticket)
  366. page.find(".js-tableBody tr.item[data-id='#{ticket.id}'] td.js-checkbox-field").click
  367. end
  368. def check_owner_agent1_shown
  369. expect(page).to have_select('owner_id', text: agent_1.fullname)
  370. expect(page).to have_no_select('owner_id', text: agent_2.fullname)
  371. end
  372. def check_owner_agent2_shown
  373. expect(page).to have_no_select('owner_id', text: agent_1.fullname)
  374. expect(page).to have_select('owner_id', text: agent_2.fullname)
  375. end
  376. def check_owner_field
  377. check_owner_empty
  378. click_ticket(ticket_1)
  379. check_owner_agent1_shown
  380. click_ticket(ticket_1)
  381. click_ticket(ticket_2)
  382. check_owner_agent2_shown
  383. end
  384. context 'when search is used' do
  385. before do
  386. visit '#search/4054'
  387. end
  388. it 'does show the correct owner selection for each bulk action' do
  389. check_owner_field
  390. end
  391. end
  392. context 'when ticket overview is used' do
  393. before do
  394. visit '#ticket/view/all_unassigned'
  395. end
  396. it 'does show the correct owner selection for each bulk action' do
  397. check_owner_field
  398. end
  399. end
  400. end
  401. # https://github.com/zammad/zammad/issues/4264
  402. describe 'Keeps selected sorting' do
  403. before do
  404. fill_in id: 'global-search', with: 'Nico'
  405. click_on 'Show Search Details'
  406. find('.table-column-title', text: 'TITLE').click
  407. end
  408. it 'when switching to other taskbar keep sorting' do
  409. visit "ticket/zoom/#{Ticket.first.id}"
  410. click_on 'Nico'
  411. wait_for_rerender
  412. within('.table-column-head', text: 'TITLE') do
  413. expect(page).to have_css('.table-sort-arrow')
  414. end
  415. end
  416. it 'when switching to other search tab keep sorting' do
  417. within :active_content do
  418. find('.js-tab', text: 'User').click
  419. find('.js-tab', text: 'Ticket').click
  420. end
  421. # no need to wait for rerender in this case
  422. within('.table-column-head', text: 'TITLE') do
  423. expect(page).to have_css('.table-sort-arrow')
  424. end
  425. end
  426. it 'when changing search query clear sorting' do
  427. within :active_content do
  428. find('.js-search').fill_in with: 'Nicole'
  429. end
  430. wait_for_rerender
  431. within('.table-column-head', text: 'TITLE') do
  432. expect(page).to have_no_css('.table-sort-arrow')
  433. end
  434. end
  435. it 'when changing search query after navigation away-and-back clear sorting' do
  436. visit "ticket/zoom/#{Ticket.first.id}"
  437. click_on 'Nico'
  438. within :active_content do
  439. find('.js-search').fill_in with: 'Nicole'
  440. end
  441. wait_for_rerender
  442. within('.table-column-head', text: 'TITLE') do
  443. expect(page).to have_no_css('.table-sort-arrow')
  444. end
  445. end
  446. def wait_for_rerender
  447. elem = find('.detail-search table')
  448. wait.until do
  449. elem.base.obscured?
  450. rescue *page.driver.invalid_element_errors
  451. true
  452. end
  453. end
  454. end
  455. describe 'Admin user can not find user or organization in the search bar #4574' do
  456. let(:admin) { create(:admin_only) }
  457. let(:agent) { create(:agent, groups: [Group.first]) }
  458. let(:organization) { create(:organization, name: SecureRandom.uuid) }
  459. let(:customer) { create(:customer, organization: organization, firstname: SecureRandom.uuid) }
  460. let(:ticket) { create(:ticket, title: SecureRandom.uuid, customer: customer, group: Group.first) }
  461. before do
  462. visit '#dashboard'
  463. end
  464. context 'when customer', authenticated_as: :authenticate do
  465. def authenticate
  466. organization && customer && ticket
  467. searchindex_model_reload([Ticket, Organization, User])
  468. customer
  469. end
  470. it 'does find the ticket' do
  471. fill_in id: 'global-search', with: ticket.title
  472. expect(page.find('.global-search-menu')).to have_content(ticket.title)
  473. end
  474. it 'does not find the customer' do
  475. fill_in id: 'global-search', with: customer.firstname
  476. expect(page.find('.global-search-menu')).to have_no_content(customer.firstname)
  477. end
  478. it 'does not find the organization' do
  479. fill_in id: 'global-search', with: organization.name
  480. expect(page.find('.global-search-menu')).to have_no_content(organization.name)
  481. end
  482. end
  483. context 'when agent', authenticated_as: :authenticate do
  484. def authenticate
  485. organization && customer && ticket
  486. searchindex_model_reload([Ticket, Organization, User])
  487. agent
  488. end
  489. it 'does find the ticket' do
  490. fill_in id: 'global-search', with: ticket.title
  491. expect(page.find('.global-search-menu')).to have_content(ticket.title)
  492. end
  493. it 'does find the customer' do
  494. fill_in id: 'global-search', with: customer.firstname
  495. expect(page.find('.global-search-menu')).to have_content(customer.firstname)
  496. end
  497. it 'does find the organization' do
  498. fill_in id: 'global-search', with: organization.name
  499. expect(page.find('.global-search-menu')).to have_content(organization.name)
  500. end
  501. end
  502. context 'when admin only', authenticated_as: :authenticate do
  503. def authenticate
  504. organization && customer && ticket
  505. searchindex_model_reload([Ticket, Organization, User])
  506. admin
  507. end
  508. it 'does not find the ticket' do
  509. fill_in id: 'global-search', with: ticket.title
  510. expect(page.find('.global-search-menu')).to have_no_content(ticket.title)
  511. end
  512. it 'does find the customer' do
  513. fill_in id: 'global-search', with: customer.firstname
  514. expect(page.find('.global-search-menu')).to have_content(customer.firstname)
  515. end
  516. it 'does find the organization' do
  517. fill_in id: 'global-search', with: organization.name
  518. expect(page.find('.global-search-menu')).to have_content(organization.name)
  519. end
  520. end
  521. end
  522. describe 'popover closes when item is opened', authenticated_as: :authenticate do
  523. let(:agent) { create(:agent, groups: Group.all) }
  524. def authenticate
  525. ticket_1 && ticket_2
  526. agent
  527. end
  528. before do
  529. fill_in id: 'global-search', with: 'Testing'
  530. end
  531. it 'closes popover when item is clicked' do
  532. elem = first('a.nav-tab.ticket-popover')
  533. popover_on_hover(elem)
  534. expect(page).to have_css('.popover')
  535. elem.click
  536. expect(page).to have_no_css('.popover')
  537. end
  538. it 'closes popover when item is opened via keyboard' do
  539. first('a.nav-tab.ticket-popover') # ensure search results are visible
  540. send_keys(:down) # go to detailed search
  541. send_keys(:down) # go to first ticket
  542. expect(page).to have_css('.popover')
  543. send_keys(:enter) # open
  544. expect(page).to have_no_css('.popover')
  545. end
  546. end
  547. end