zoom_spec.rb 87 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. require 'system/examples/core_workflow_examples'
  4. RSpec.describe 'Ticket zoom', type: :system do
  5. context 'when ticket has an attachment' do
  6. let(:group) { Group.find_by(name: 'Users') }
  7. let(:ticket) { create(:ticket, group: group) }
  8. let(:article) { create(:ticket_article, ticket: ticket) }
  9. let(:attachment_name) { 'some_file.txt' }
  10. before do
  11. create(:store,
  12. object: 'Ticket::Article',
  13. o_id: article.id,
  14. data: 'some content',
  15. filename: attachment_name,
  16. preferences: {
  17. 'Content-Type' => 'text/plain',
  18. })
  19. end
  20. context 'article was already forwarded once' do
  21. before do
  22. visit "#ticket/zoom/#{ticket.id}"
  23. within(:active_content) do
  24. find('a[data-type=emailForward]').click
  25. click('.js-reset')
  26. have_no_css('.js-reset')
  27. end
  28. end
  29. it 'adds attachments when forwarding multiple times' do
  30. within(:active_content) do
  31. find('a[data-type=emailForward]').click
  32. end
  33. within('.js-writeArea') do
  34. expect(page).to have_text attachment_name
  35. end
  36. end
  37. end
  38. end
  39. context 'when using the sidebar' do
  40. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users'), customer: create(:customer, :with_org)) }
  41. before do
  42. Setting.set("#{service_name}_integration", true) if defined? service_name
  43. visit "#ticket/zoom/#{ticket.id}"
  44. end
  45. it 'does show the edit link for the customer' do
  46. click '.tabsSidebar-tab[data-tab=customer]'
  47. click '#userAction'
  48. click_on 'Edit Customer'
  49. modal_ready
  50. end
  51. it 'does show the edit link for the organization' do
  52. click '.tabsSidebar-tab[data-tab=organization]'
  53. click '#userAction'
  54. click_on 'Edit Organization'
  55. modal_ready
  56. end
  57. %w[idoit gitlab github].each do |service_name|
  58. it "#{service_name} tab is hidden" do
  59. expect(page).to have_no_css(".tabsSidebar-tab[data-tab=#{service_name}]")
  60. end
  61. context "when #{service_name} is enabled" do
  62. let(:service_name) { service_name }
  63. context 'when agent' do
  64. it "#{service_name} tab is visible" do
  65. expect(page).to have_css(".tabsSidebar-tab[data-tab=#{service_name}]")
  66. end
  67. end
  68. context 'when customer', authenticated_as: :customer do
  69. let(:customer) { create(:customer) }
  70. it "#{service_name} tab is hidden" do
  71. expect(page).to have_no_css(".tabsSidebar-tab[data-tab=#{service_name}]")
  72. end
  73. end
  74. end
  75. end
  76. end
  77. context 'when ticket has a calendar attachment' do
  78. let(:group) { Group.find_by(name: 'Users') }
  79. let(:store_file_content_name) do
  80. Rails.root.join('spec/fixtures/files/calendar/basic.ics').read
  81. end
  82. let(:store_file_name) { 'basic.ics' }
  83. let(:expected_event) do
  84. {
  85. 'title' => 'Test Summary',
  86. 'location' => 'https://us.zoom.us/j/example?pwd=test',
  87. 'attendees' => ['M.bob@example.com', 'J.doe@example.com'],
  88. 'organizer' => 'f.sample@example.com',
  89. 'description' => 'Test description'
  90. }
  91. end
  92. let(:ticket) { create(:ticket, group: group) }
  93. let(:article) { create(:ticket_article, ticket: ticket) }
  94. before do
  95. create(:store,
  96. object: 'Ticket::Article',
  97. o_id: article.id,
  98. data: store_file_content_name,
  99. filename: store_file_name,
  100. preferences: {
  101. 'Content-Type' => 'text/calendar',
  102. })
  103. visit "#ticket/zoom/#{ticket.id}"
  104. end
  105. it 'has an attached calendar file' do
  106. within :active_ticket_article, article do
  107. within '.attachment.file-calendar' do
  108. expect(page).to have_text(store_file_name)
  109. end
  110. end
  111. end
  112. it 'shows a preview button for the calendar file' do
  113. within :active_ticket_article, article do
  114. within '.attachment.file-calendar' do
  115. expect(page).to have_button('Preview')
  116. end
  117. end
  118. end
  119. context 'when calendar preview button is clicked' do
  120. before do
  121. within :active_ticket_article, article do
  122. within '.attachment.file-calendar' do
  123. click_on 'Preview'
  124. end
  125. end
  126. end
  127. it 'shows calender data in the model' do
  128. in_modal do
  129. expect(page).to have_text expected_event['title']
  130. expect(page).to have_text expected_event['location']
  131. expected_event['attendees'].each { |attendee| expect(page).to have_text attendee }
  132. expect(page).to have_text expected_event['organizer']
  133. expect(page).to have_text expected_event['description']
  134. end
  135. click '.js-cancel'
  136. end
  137. end
  138. end
  139. context 'replying' do
  140. context 'Group without signature' do
  141. let(:ticket) { create(:ticket) }
  142. let(:current_user) { create(:agent, password: 'test', groups: [ticket.group]) }
  143. before do
  144. # initial article to reply to
  145. create(:ticket_article, ticket: ticket)
  146. end
  147. it 'ensures that text input opens on multiple replies', authenticated_as: :current_user do
  148. visit "ticket/zoom/#{ticket.id}"
  149. 2.times do |article_offset|
  150. articles_existing = 1
  151. articles_expected = articles_existing + (article_offset + 1)
  152. all('a[data-type=emailReply]').last.click
  153. # wait till input box expands completely
  154. find('.attachmentPlaceholder-label').in_fixed_position
  155. expect(page).to have_no_css('.attachmentPlaceholder-hint')
  156. find('.articleNewEdit-body').send_keys('Some reply')
  157. click '.js-submit'
  158. expect(page).to have_css('.ticket-article-item', count: articles_expected)
  159. end
  160. end
  161. end
  162. context 'Group with signature', authenticated_as: :user do
  163. let(:signature_body) { 'Sample signature here' }
  164. let(:signature) { create(:signature, body: signature_body) }
  165. let(:group) { create(:group, signature: signature) }
  166. let(:ticket) { create(:ticket, group: group) }
  167. let(:user) { create(:agent, groups: [group]) }
  168. before do
  169. visit "ticket/zoom/#{ticket.id}"
  170. click '.attachmentPlaceholder'
  171. end
  172. it 'removes signature when switching from email reply to phone' do
  173. click '.js-selectableTypes'
  174. click '.js-articleTypeItem[data-value=email]'
  175. within :richtext do
  176. expect(page).to have_text(signature_body)
  177. end
  178. click '.js-selectableTypes'
  179. click '.js-articleTypeItem[data-value=phone]'
  180. within :richtext do
  181. expect(page).to have_no_text(signature_body)
  182. end
  183. end
  184. it 'adds signature when switching from phone to email reply' do
  185. within :richtext do
  186. expect(page).to have_no_text(signature_body)
  187. end
  188. click '.js-selectableTypes'
  189. click '.js-articleTypeItem[data-value=email]'
  190. within :richtext do
  191. expect(page).to have_text(signature_body)
  192. end
  193. end
  194. end
  195. context 'to inbound phone call', authenticated_as: -> { agent }, current_user_id: -> { agent.id } do
  196. let(:agent) { create(:agent, groups: [Group.first]) }
  197. let(:customer) { create(:customer) }
  198. let(:ticket) { create(:ticket, customer: customer, group: agent.groups.first) }
  199. let!(:article) { create(:ticket_article, :inbound_phone, ticket: ticket) }
  200. before do
  201. create(:customer, active: false)
  202. end
  203. it 'goes to customer email' do
  204. visit "ticket/zoom/#{ticket.id}"
  205. within :active_ticket_article, article do
  206. click '.js-ArticleAction[data-type=emailReply]'
  207. end
  208. within :active_content do
  209. within '.article-new' do
  210. expect(find('[name=to]', visible: :all).value).to eq customer.email
  211. end
  212. end
  213. end
  214. it 'check active and inactive user in TO-field' do
  215. visit "ticket/zoom/#{ticket.id}"
  216. within :active_ticket_article, article do
  217. click '.js-ArticleAction[data-type=emailReply]'
  218. end
  219. within :active_content do
  220. within '.article-new' do
  221. find('[name=to] ~ .ui-autocomplete-input').fill_in with: '**'
  222. end
  223. end
  224. expect(page).to have_css('ul.ui-autocomplete > li.ui-menu-item', minimum: 2)
  225. expect(page).to have_css('ul.ui-autocomplete > li.ui-menu-item.is-inactive', count: 1)
  226. end
  227. end
  228. context 'to outbound phone call', authenticated_as: -> { agent }, current_user_id: -> { agent.id } do
  229. let(:agent) { create(:agent, groups: [Group.first]) }
  230. let(:customer) { create(:customer) }
  231. let(:ticket) { create(:ticket, customer: customer, group: agent.groups.first) }
  232. let!(:article) { create(:ticket_article, :outbound_phone, ticket: ticket) }
  233. it 'goes to customer email' do
  234. visit "ticket/zoom/#{ticket.id}"
  235. within :active_ticket_article, article do
  236. click '.js-ArticleAction[data-type=emailReply]'
  237. end
  238. within :active_content do
  239. within '.article-new' do
  240. expect(find('[name=to]', visible: :all).value).to eq customer.email
  241. end
  242. end
  243. end
  244. end
  245. context 'scrollPageHeader disappears when answering via email #3736' do
  246. let(:ticket) do
  247. ticket = create(:ticket, group: Group.first)
  248. create_list(:ticket_article, 15, ticket: ticket)
  249. ticket
  250. end
  251. before do
  252. visit "ticket/zoom/#{ticket.id}"
  253. end
  254. it 'does reset the scrollPageHeader on rerender of the ticket' do
  255. select User.find_by(email: 'admin@example.com').fullname, from: 'Owner'
  256. find('.js-textarea').send_keys('test 1234')
  257. find('.js-submit').click
  258. expect(page).to have_css('div.scrollPageHeader .js-ticketTitleContainer')
  259. end
  260. end
  261. end
  262. describe 'delete article', authenticated_as: :authenticate do
  263. let(:group) { Group.first }
  264. let(:admin) { create(:admin, groups: [group]) }
  265. let(:agent) { create(:agent, groups: [group]) }
  266. let(:other_agent) { create(:agent, groups: [group]) }
  267. let(:customer) { create(:customer) }
  268. let(:article) { send(item) }
  269. def authenticate
  270. Setting.set('ui_ticket_zoom_article_delete_timeframe', setting_delete_timeframe) if defined?(setting_delete_timeframe)
  271. article
  272. user
  273. end
  274. def article_communication
  275. create_ticket_article(sender_name: 'Agent', internal: false, type_name: 'email', updated_by: customer)
  276. end
  277. def article_note_self
  278. create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note', updated_by: user)
  279. end
  280. def article_note_other
  281. create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note', updated_by: other_agent)
  282. end
  283. def article_note_customer
  284. create_ticket_article(sender_name: 'Customer', internal: false, type_name: 'note', updated_by: customer)
  285. end
  286. def article_note_communication_self
  287. create(:ticket_article_type, name: 'note_communication', communication: true)
  288. create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note_communication', updated_by: user)
  289. end
  290. def article_note_communication_other
  291. create(:ticket_article_type, name: 'note_communication', communication: true)
  292. create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note_communication', updated_by: other_agent)
  293. end
  294. def create_ticket_article(sender_name:, internal:, type_name:, updated_by:)
  295. UserInfo.current_user_id = updated_by.id
  296. ticket = create(:ticket, group: group, customer: customer)
  297. create(:ticket_article,
  298. sender_name: sender_name, internal: internal, type_name: type_name, ticket: ticket,
  299. body: "to be deleted #{offset} #{item}",
  300. created_at: offset.ago, updated_at: offset.ago)
  301. end
  302. context 'going through full stack' do
  303. context 'as admin' do
  304. let(:user) { admin }
  305. let(:item) { 'article_note_self' }
  306. let(:offset) { 0.minutes }
  307. it 'succeeds' do
  308. ensure_websocket do
  309. visit "ticket/zoom/#{article.ticket.id}"
  310. end
  311. within :active_ticket_article, article do
  312. click '.js-ArticleAction[data-type=delete]'
  313. end
  314. in_modal do
  315. click '.js-submit'
  316. end
  317. wait.until_disappears { find :active_ticket_article, article, wait: false }
  318. end
  319. end
  320. end
  321. context 'verifying permissions matrix' do
  322. shared_examples 'according to permission matrix' do |item:, expects_visible:, offset:, description:|
  323. context "looking at #{description} #{item}" do
  324. let(:item) { item }
  325. let(:offset) { offset }
  326. let(:matcher) { expects_visible ? :have_css : :have_no_css }
  327. it expects_visible ? 'delete button is visible' : 'delete button is not visible' do
  328. visit "ticket/zoom/#{article.ticket.id}"
  329. find("#article-#{article.id}")
  330. within :active_ticket_article, article do
  331. expect(page).to send(matcher, '.js-ArticleAction[data-type=delete]', wait: 0)
  332. end
  333. end
  334. end
  335. end
  336. shared_examples 'deleting ticket article' do |item:, now:, later:, much_later:|
  337. include_examples 'according to permission matrix', item: item, expects_visible: now, offset: 0.minutes, description: 'just created'
  338. include_examples 'according to permission matrix', item: item, expects_visible: later, offset: 6.minutes, description: 'few minutes old'
  339. include_examples 'according to permission matrix', item: item, expects_visible: much_later, offset: 11.minutes, description: 'very old'
  340. end
  341. context 'as admin' do
  342. let(:user) { admin }
  343. include_examples 'deleting ticket article',
  344. item: 'article_communication',
  345. now: false, later: false, much_later: false
  346. include_examples 'deleting ticket article',
  347. item: 'article_note_self',
  348. now: true, later: true, much_later: false
  349. include_examples 'deleting ticket article',
  350. item: 'article_note_other',
  351. now: false, later: false, much_later: false
  352. include_examples 'deleting ticket article',
  353. item: 'article_note_customer',
  354. now: false, later: false, much_later: false
  355. include_examples 'deleting ticket article',
  356. item: 'article_note_communication_self',
  357. now: true, later: true, much_later: false
  358. include_examples 'deleting ticket article',
  359. item: 'article_note_communication_other',
  360. now: false, later: false, much_later: false
  361. end
  362. context 'as agent' do
  363. let(:user) { agent }
  364. include_examples 'deleting ticket article',
  365. item: 'article_communication',
  366. now: false, later: false, much_later: false
  367. include_examples 'deleting ticket article',
  368. item: 'article_note_self',
  369. now: true, later: true, much_later: false
  370. include_examples 'deleting ticket article',
  371. item: 'article_note_other',
  372. now: false, later: false, much_later: false
  373. include_examples 'deleting ticket article',
  374. item: 'article_note_customer',
  375. now: false, later: false, much_later: false
  376. include_examples 'deleting ticket article',
  377. item: 'article_note_communication_self',
  378. now: true, later: true, much_later: false
  379. include_examples 'deleting ticket article',
  380. item: 'article_note_communication_other',
  381. now: false, later: false, much_later: false
  382. end
  383. context 'as customer' do
  384. let(:user) { customer }
  385. include_examples 'deleting ticket article',
  386. item: 'article_communication',
  387. now: false, later: false, much_later: false
  388. include_examples 'deleting ticket article',
  389. item: 'article_note_customer',
  390. now: false, later: false, much_later: false
  391. end
  392. context 'with custom offset' do
  393. let(:setting_delete_timeframe) { 6_000 }
  394. context 'as admin' do
  395. let(:user) { admin }
  396. include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: true, offset: 5000.seconds, description: 'outside of delete timeframe'
  397. include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: false, offset: 8000.seconds, description: 'outside of delete timeframe'
  398. end
  399. context 'as agent' do
  400. let(:user) { agent }
  401. include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: true, offset: 5000.seconds, description: 'outside of delete timeframe'
  402. include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: false, offset: 8000.seconds, description: 'outside of delete timeframe'
  403. end
  404. end
  405. context 'with timeframe as 0' do
  406. let(:setting_delete_timeframe) { 0 }
  407. context 'as agent' do
  408. let(:user) { agent }
  409. include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: true, offset: 99.days, description: 'long after'
  410. end
  411. end
  412. end
  413. context 'button is hidden on the go' do
  414. let(:setting_delete_timeframe) { 10 }
  415. let(:user) { agent }
  416. let(:item) { 'article_note_self' }
  417. let!(:article) { send(item) }
  418. let(:offset) { 0.seconds }
  419. it 'successfully' do
  420. visit "ticket/zoom/#{article.ticket.id}"
  421. within :active_ticket_article, article do
  422. find '.js-ArticleAction[data-type=delete]' # make sure delete button did show up
  423. expect(page).to have_no_css('.js-ArticleAction[data-type=delete]')
  424. end
  425. end
  426. end
  427. end
  428. describe 'forwarding article with an image' do
  429. let(:ticket_article_body) do
  430. filename = 'squares.png'
  431. file = Rails.root.join("spec/fixtures/files/image/#{filename}").binread
  432. ext = File.extname(filename)[1...]
  433. base64 = Base64.encode64(file).delete("\n")
  434. "<img style='width: 1004px; max-width: 100%;' src=\\\"data:image/#{ext};base64,#{base64}\\\"><br>"
  435. end
  436. def current_ticket
  437. Ticket.find current_url.split('/').last
  438. end
  439. def create_ticket
  440. visit '#ticket/create'
  441. within :active_content do
  442. find('[data-type=email-out]').click
  443. find('[name=title]').fill_in with: 'Title'
  444. find('[name=customer_id_completion]').fill_in with: 'customer@example.com'
  445. set_tree_select_value('group_id', Group.first.name)
  446. find(:richtext).execute_script "this.innerHTML = \"#{ticket_article_body}\""
  447. find('.js-submit').click
  448. end
  449. end
  450. def forward
  451. within :active_content do
  452. find('.textBubble-content .richtext-content')
  453. click '.js-ArticleAction[data-type=emailForward]'
  454. fill_in 'To', with: 'customer@example.com'
  455. find('.js-submit').click
  456. end
  457. end
  458. def images_identical?(image_a, image_b)
  459. return false if image_a.height != image_b.height
  460. return false if image_a.width != image_b.width
  461. image_a.height.times do |y|
  462. image_a.row(y).each_with_index do |pixel, x|
  463. return false if pixel != image_b[x, y]
  464. end
  465. end
  466. true
  467. end
  468. it 'keeps image intact' do
  469. create_ticket
  470. forward
  471. images = current_ticket.articles.map do |article|
  472. ChunkyPNG::Image.from_string article.attachments.first.content
  473. end
  474. expect(images_identical?(images.first, images.second)).to be(true)
  475. end
  476. end
  477. # https://github.com/zammad/zammad/issues/3335
  478. context 'ticket state sort order maintained when locale is de-de', authenticated_as: :user do
  479. context 'when existing ticket is open' do
  480. let(:user) { create(:customer, preferences: { locale: 'de-de' }) }
  481. let(:ticket) { create(:ticket, customer: user) }
  482. it 'shows ticket state dropdown options in sorted translated alphabetically order' do
  483. visit "ticket/zoom/#{ticket.id}"
  484. within :active_content, '.tabsSidebar' do
  485. expect(all('select[name=state_id] option').map(&:text)).to eq(%w[geschlossen neu offen])
  486. end
  487. end
  488. end
  489. context 'when a new ticket is created' do
  490. let(:user) { create(:agent, preferences: { locale: 'de-de' }, groups: [permitted_group]) }
  491. let(:permitted_group) { create(:group) }
  492. it 'shows ticket state dropdown options in sorted order' do
  493. visit 'ticket/create'
  494. expect(all('select[name=state_id] option').map(&:text)).to eq ['-', 'geschlossen', 'neu', 'offen', 'warten auf Erinnerung', 'warten auf Schließen']
  495. end
  496. end
  497. end
  498. context 'object manager attribute permission view' do
  499. let!(:group_users) { Group.find_by(name: 'Users') }
  500. shared_examples 'shows attributes and values for agent view and editable' do
  501. it 'shows attributes and values for agent view and editable', authenticated_as: :current_user do
  502. visit "ticket/zoom/#{ticket.id}"
  503. refresh # refresh to have assets generated for ticket
  504. expect(page).to have_select('state_id', options: ['new', 'open', 'pending reminder', 'pending close', 'closed'])
  505. expect(page).to have_select('priority_id')
  506. expect(page).to have_select('owner_id')
  507. expect(page).to have_css('div.tabsSidebar-tab[data-tab=customer]')
  508. end
  509. end
  510. shared_examples 'shows attributes and values for agent view but disabled' do
  511. it 'shows attributes and values for agent view but disabled', authenticated_as: :current_user do
  512. visit "ticket/zoom/#{ticket.id}"
  513. refresh # refresh to have assets generated for ticket
  514. expect(page).to have_select('state_id', disabled: true)
  515. expect(page).to have_select('priority_id', disabled: true)
  516. expect(page).to have_select('owner_id', disabled: true)
  517. expect(page).to have_css('div.tabsSidebar-tab[data-tab=customer]')
  518. end
  519. end
  520. shared_examples 'shows attributes and values for customer view' do
  521. it 'shows attributes and values for customer view', authenticated_as: :current_user do
  522. visit "ticket/zoom/#{ticket.id}"
  523. refresh # refresh to have assets generated for ticket
  524. expect(page).to have_select('state_id', options: %w[new open closed])
  525. expect(page).to have_no_select('priority_id')
  526. expect(page).to have_no_select('owner_id')
  527. expect(page).to have_no_css('div.tabsSidebar-tab[data-tab=customer]')
  528. end
  529. end
  530. context 'as customer' do
  531. let!(:current_user) { create(:customer) }
  532. let(:ticket) { create(:ticket, customer: current_user) }
  533. include_examples 'shows attributes and values for customer view'
  534. end
  535. context 'as agent with full permissions' do
  536. let(:current_user) { create(:agent, groups: [ group_users ]) }
  537. let(:ticket) { create(:ticket, group: group_users) }
  538. include_examples 'shows attributes and values for agent view and editable'
  539. end
  540. context 'as agent with change permissions' do
  541. let!(:current_user) { create(:agent) }
  542. let(:ticket) { create(:ticket, group: group_users) }
  543. before do
  544. current_user.group_names_access_map = {
  545. group_users.name => %w[read change],
  546. }
  547. end
  548. include_examples 'shows attributes and values for agent view and editable'
  549. end
  550. context 'as agent with read permissions' do
  551. let!(:current_user) { create(:agent) }
  552. let(:ticket) { create(:ticket, group: group_users) }
  553. before do
  554. current_user.group_names_access_map = {
  555. group_users.name => 'read',
  556. }
  557. end
  558. include_examples 'shows attributes and values for agent view but disabled'
  559. end
  560. context 'as agent+customer with full permissions' do
  561. let!(:current_user) { create(:agent_and_customer, groups: [ group_users ]) }
  562. context 'normal ticket' do
  563. let(:ticket) { create(:ticket, group: group_users) }
  564. include_examples 'shows attributes and values for agent view and editable'
  565. end
  566. context 'ticket where current_user is also customer' do
  567. let(:ticket) { create(:ticket, customer: current_user, group: group_users) }
  568. include_examples 'shows attributes and values for agent view and editable'
  569. end
  570. end
  571. context 'as agent+customer with change permissions' do
  572. let!(:current_user) { create(:agent_and_customer) }
  573. before do
  574. current_user.group_names_access_map = {
  575. group_users.name => %w[read change],
  576. }
  577. end
  578. context 'normal ticket' do
  579. let(:ticket) { create(:ticket, group: group_users) }
  580. include_examples 'shows attributes and values for agent view and editable'
  581. end
  582. context 'ticket where current_user is also customer' do
  583. let(:ticket) { create(:ticket, customer: current_user, group: group_users) }
  584. include_examples 'shows attributes and values for agent view and editable'
  585. end
  586. end
  587. context 'as agent+customer with read permissions' do
  588. let!(:current_user) { create(:agent_and_customer) }
  589. before do
  590. current_user.group_names_access_map = {
  591. group_users.name => 'read',
  592. }
  593. end
  594. context 'normal ticket' do
  595. let(:ticket) { create(:ticket, group: group_users) }
  596. include_examples 'shows attributes and values for agent view but disabled'
  597. end
  598. context 'ticket where current_user is also customer' do
  599. let(:ticket) { create(:ticket, customer: current_user, group: group_users) }
  600. include_examples 'shows attributes and values for agent view but disabled'
  601. end
  602. end
  603. context 'as agent+customer but only customer for the ticket (no agent access)' do
  604. let!(:current_user) { create(:agent_and_customer) }
  605. let(:ticket) { create(:ticket, customer: current_user) }
  606. include_examples 'shows attributes and values for customer view'
  607. end
  608. end
  609. describe 'note visibility', authenticated_as: :customer do
  610. context 'when logged in as a customer' do
  611. let(:customer) { create(:customer) }
  612. let(:ticket) { create(:ticket, customer: customer) }
  613. let!(:ticket_article) { create(:ticket_article, ticket: ticket) }
  614. let!(:ticket_note) { create(:ticket_article, ticket: ticket, internal: true, type_name: 'note') }
  615. it 'previously created private note is not visible' do
  616. visit "ticket/zoom/#{ticket_article.ticket.id}"
  617. expect(page).to have_no_selector(:active_ticket_article, ticket_note)
  618. end
  619. it 'previously created private note shows up via WS push' do
  620. visit "ticket/zoom/#{ticket_article.ticket.id}"
  621. ensure_websocket
  622. # make sure ticket is done loading and change will be pushed via WS
  623. find(:active_ticket_article, ticket_article)
  624. ticket_note.update!(internal: false)
  625. expect(page).to have_selector(:active_ticket_article, ticket_note)
  626. end
  627. end
  628. end
  629. # https://github.com/zammad/zammad/issues/3012
  630. describe 'article type selection' do
  631. context 'when logged in as a customer', authenticated_as: :customer do
  632. let(:customer) { create(:customer) }
  633. let(:ticket) { create(:ticket, customer: customer) }
  634. it 'hides button for single choice' do
  635. visit "ticket/zoom/#{ticket.id}"
  636. find('.articleNewEdit-body').send_keys('Some reply')
  637. expect(page).to have_no_selector('.js-selectedArticleType')
  638. end
  639. end
  640. context 'when logged in as an agent' do
  641. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  642. it 'shows button for multiple choices' do
  643. visit "ticket/zoom/#{ticket.id}"
  644. find('.articleNewEdit-body').send_keys('Some reply')
  645. expect(page).to have_css('.js-selectedArticleType')
  646. end
  647. end
  648. end
  649. # https://github.com/zammad/zammad/issues/3260
  650. describe 'next in overview macro changes URL', authenticated_as: :authenticate do
  651. let(:next_ticket) { create(:ticket, title: 'next Ticket', group: Group.first) }
  652. let(:macro) { create(:macro, name: 'next macro', ux_flow_next_up: 'next_from_overview') }
  653. def authenticate
  654. next_ticket && macro
  655. true
  656. end
  657. it 'to next Ticket ID' do
  658. visit 'ticket/view/all_unassigned'
  659. click_on 'Welcome to Zammad!'
  660. click '.js-openDropdownMacro'
  661. find(:macro, macro.id).click
  662. wait(5, interval: 1).until_constant { current_url }
  663. expect(current_url).to include("ticket/zoom/#{next_ticket.id}")
  664. end
  665. end
  666. # https://github.com/zammad/zammad/issues/3279
  667. describe 'previous/next clickability when at last or first ticket' do
  668. let(:ticket_a) { create(:ticket, title: 'ticket a', group: Group.first) }
  669. let(:ticket_b) { create(:ticket, title: 'ticket b', group: Group.first) }
  670. before do
  671. ticket_a && ticket_b
  672. visit 'ticket/view/all_unassigned'
  673. end
  674. it 'previous is not clickable for the first item' do
  675. open_nth_item(0)
  676. expect(page).to have_css('.pagination .btn--split--first.is-disabled')
  677. end
  678. it 'next is clickable for the first item' do
  679. open_nth_item(0)
  680. expect { click '.pagination .btn--split--last' }.to change { page.find('.content.active')[:id] }
  681. end
  682. it 'previous is clickable for the middle item' do
  683. open_nth_item(1)
  684. expect { click '.pagination .btn--split--first' }.to change { page.find('.content.active')[:id] }
  685. end
  686. it 'next is clickable for the middle item' do
  687. open_nth_item(1)
  688. expect { click '.pagination .btn--split--last' }.to change { page.find('.content.active')[:id] }
  689. end
  690. it 'previous is clickable for the last item' do
  691. open_nth_item(2)
  692. expect { click '.pagination .btn--split--first' }.to change { page.find('.content.active')[:id] }
  693. end
  694. it 'next is not clickable for the last item' do
  695. open_nth_item(2)
  696. expect(page).to have_css('.pagination .btn--split--last.is-disabled')
  697. end
  698. def open_nth_item(nth)
  699. within :active_content do
  700. find_all('.table tr.item a[href^="#ticket/zoom"]')[nth].click
  701. end
  702. await_empty_ajax_queue
  703. end
  704. end
  705. # https://github.com/zammad/zammad/issues/3267
  706. describe 'previous/next buttons are added when open ticket is opened from overview' do
  707. let(:ticket_a) { create(:ticket, title: 'ticket a', group: Group.first) }
  708. let(:ticket_b) { create(:ticket, title: 'ticket b', group: Group.first) }
  709. # prepare an opened ticket and go to overview
  710. before do
  711. ticket_a && ticket_b
  712. visit "ticket/zoom/#{ticket_a.id}"
  713. visit 'ticket/view/all_unassigned'
  714. end
  715. it 'adds previous/next buttons to existing ticket' do
  716. within :active_content do
  717. click_on ticket_a.title
  718. expect(page).to have_css('.pagination-counter')
  719. end
  720. end
  721. it 'keeps previous/next buttons when navigating to overview ticket from elsewhere' do
  722. within :active_content do
  723. click_on ticket_a.title
  724. visit 'dashboard'
  725. visit "ticket/zoom/#{ticket_a.id}"
  726. expect(page).to have_css('.pagination-counter')
  727. end
  728. end
  729. end
  730. # https://github.com/zammad/zammad/issues/2942
  731. describe 'attachments are lost in specific conditions' do
  732. let(:ticket) { create(:ticket, group: Group.first) }
  733. it 'attachment is retained when forwarding a fresh article' do
  734. ensure_websocket do
  735. visit "ticket/zoom/#{ticket.id}"
  736. end
  737. # add an article, forcing reset of form_id
  738. # click in the upper most upper left corner of the article create textbox
  739. # (that works for both Firefox and Chrome)
  740. # to avoid clicking on attachment upload
  741. find('.js-writeArea').click(x: 5, y: 5)
  742. # wait for propagateOpenTextarea to be completed
  743. find('.attachmentPlaceholder-label').in_fixed_position
  744. expect(page).to have_no_css('.attachmentPlaceholder-hint')
  745. # write article content
  746. find('.articleNewEdit-body').send_keys('Some reply')
  747. click '.js-submit'
  748. # wait for article to be added to the page
  749. expect(page).to have_css('.ticket-article-item', count: 1)
  750. # create a on-the-fly article with attachment that will get pushed to open browser
  751. article1 = create(:ticket_article, ticket: ticket)
  752. create(:store,
  753. object: 'Ticket::Article',
  754. o_id: article1.id,
  755. data: 'some content',
  756. filename: 'some_file.txt',
  757. preferences: {
  758. 'Content-Type' => 'text/plain',
  759. })
  760. # wait for article to be added to the page
  761. expect(page).to have_css('.ticket-article-item', count: 2)
  762. # click on forward of created article
  763. within :active_ticket_article, article1 do
  764. find('a[data-type=emailForward]').click
  765. end
  766. # wait for propagateOpenTextarea to be completed
  767. find('.attachmentPlaceholder-label').in_fixed_position
  768. expect(page).to have_no_css('.attachmentPlaceholder-hint')
  769. # fill forward information and create article
  770. fill_in 'To', with: 'forward@example.org'
  771. find('.articleNewEdit-body').send_keys('Forwarding with the attachment')
  772. click '.js-submit'
  773. # wait for article to be added to the page
  774. expect(page).to have_css('.ticket-article-item', count: 3)
  775. # check if attachment was forwarded successfully
  776. within :active_ticket_article, ticket.reload.articles.last do
  777. within '.attachments--list' do
  778. expect(page).to have_text('some_file.txt')
  779. end
  780. end
  781. end
  782. end
  783. describe 'mentions' do
  784. context 'when logged in as agent' do
  785. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  786. let!(:other_agent) { create(:agent, groups: [Group.find_by(name: 'Users')]) }
  787. let!(:admin) { User.find_by(email: 'admin@example.com') }
  788. before do
  789. create(:macro, name: 'Subscribe', ux_flow_next_up: 'none', perform: { 'ticket.subscribe': { value: 'current_user.id' } })
  790. create(:macro, name: 'Unsubscribe', ux_flow_next_up: 'none', perform: { 'ticket.unsubscribe': { value: 'current_user.id' } })
  791. end
  792. it 'can subscribe and unsubscribe' do
  793. ensure_websocket do
  794. visit "ticket/zoom/#{ticket.id}"
  795. # subscribe via sidebar
  796. click '.js-subscriptions .js-subscribe input'
  797. expect(page).to have_css('.js-subscriptions .js-unsubscribe input')
  798. expect(page).to have_css('.js-subscriptions span.avatar')
  799. # unsubscribe via sidebar
  800. click '.js-subscriptions .js-unsubscribe input'
  801. expect(page).to have_css('.js-subscriptions .js-subscribe input')
  802. expect(page).to have_no_selector('.js-subscriptions span.avatar')
  803. # subscribe via macro
  804. click '.js-openDropdownMacro'
  805. find(:macro, 2).click # Subscribe macro button
  806. expect(page).to have_css('.js-subscriptions span.avatar')
  807. # unsubscribe via macro
  808. click '.js-openDropdownMacro'
  809. find(:macro, 3).click # Unsubscribe macro button
  810. expect(page).to have_no_selector('.js-subscriptions span.avatar')
  811. create(:mention, mentionable: ticket, user: other_agent)
  812. expect(page).to have_css('.js-subscriptions span.avatar')
  813. # check history for mention entries
  814. click 'h2.sidebar-header-headline.js-headline'
  815. click 'li[data-type=ticket-history] a'
  816. expect(page).to have_text("created Mention → '#{admin.firstname} #{admin.lastname}'")
  817. expect(page).to have_text("removed Mention → '#{admin.firstname} #{admin.lastname}'")
  818. expect(page).to have_text("created Mention → '#{other_agent.firstname} #{other_agent.lastname}'")
  819. end
  820. end
  821. end
  822. end
  823. # https://github.com/zammad/zammad/issues/2671
  824. describe 'Pending time field in ticket sidebar', authenticated_as: :customer do
  825. let(:customer) { create(:customer) }
  826. let(:ticket) { create(:ticket, customer: customer, pending_time: 1.day.from_now, state: Ticket::State.lookup(name: 'pending reminder')) }
  827. it 'not shown to customer' do
  828. visit "ticket/zoom/#{ticket.id}"
  829. within :active_content do
  830. expect(page).to have_no_css('.controls[data-name=pending_time]')
  831. end
  832. end
  833. end
  834. describe 'Pending time field in ticket sidebar as agent' do
  835. before do
  836. ticket.update(pending_time: 1.day.from_now, state: Ticket::State.lookup(name: 'pending reminder'))
  837. visit "ticket/zoom/#{ticket.id}"
  838. end
  839. let(:ticket) { Ticket.first }
  840. # has to run asynchronously to keep both Firefox and Safari
  841. # https://github.com/zammad/zammad/issues/3414
  842. # https://github.com/zammad/zammad/issues/2887
  843. context 'when clicking timepicker component' do
  844. it 'in the first half, hours selected' do
  845. within :active_content do
  846. # timepicker messes with the dom, so don't cache the element and wait a bit.
  847. sleep 1
  848. find('.js-timepicker').click(x: -10, y: 20)
  849. sleep 0.5
  850. expect(find('.js-timepicker')).to have_selection(0..2)
  851. end
  852. end
  853. it 'in the second half, minutes selected' do
  854. within :active_content do
  855. sleep 1
  856. find('.js-timepicker').click(x: 10, y: 20)
  857. sleep 0.5
  858. expect(find('.js-timepicker')).to have_selection(3..5)
  859. end
  860. end
  861. end
  862. matcher :have_selection do
  863. match { starts_at == expected.begin && ends_at == expected.end }
  864. def starts_at
  865. actual.evaluate_script 'this.selectionStart'
  866. end
  867. def ends_at
  868. actual.evaluate_script 'this.selectionEnd'
  869. end
  870. end
  871. end
  872. describe 'Article ID URL / link' do
  873. let(:ticket) { create(:ticket, group: Group.first) }
  874. let!(:article) { create(:'ticket/article', ticket: ticket) }
  875. it 'shows Article direct link' do
  876. ensure_websocket do
  877. visit "ticket/zoom/#{ticket.id}"
  878. end
  879. url = "#{Setting.get('http_type')}://#{Setting.get('fqdn')}/#ticket/zoom/#{ticket.id}/#{article.id}"
  880. within :active_ticket_article, article do
  881. expect(page).to have_css(%(a[href="#{url}"]))
  882. end
  883. end
  884. context 'when multiple Articles are present' do
  885. let(:article_count) { 20 }
  886. let(:article_top) { ticket.articles.second }
  887. let(:article_middle) { ticket.articles[ article_count / 2 ] }
  888. let(:article_bottom) { ticket.articles.last }
  889. before do
  890. article_count.times do
  891. create(:'ticket/article', ticket: ticket, body: SecureRandom.uuid)
  892. end
  893. visit "ticket/zoom/#{ticket.id}"
  894. end
  895. def wait_for_scroll
  896. wait(5, interval: 0.2).until_constant do
  897. find('.ticketZoom').native.location.y
  898. end
  899. end
  900. def check_shown(top: false, middle: false, bottom: false)
  901. wait_for_scroll
  902. expect(page).to have_css("div#article-content-#{article_top.id} .richtext-content", obscured: !top)
  903. .and(have_css("div#article-content-#{article_middle.id} .richtext-content", obscured: !middle, wait: 0))
  904. .and(have_css("div#article-content-#{article_bottom.id} .richtext-content", obscured: !bottom, wait: 0))
  905. end
  906. it 'scrolls to top article ID' do
  907. visit "ticket/zoom/#{ticket.id}/#{article_top.id}"
  908. check_shown(top: true)
  909. end
  910. it 'scrolls to middle article ID' do
  911. visit "ticket/zoom/#{ticket.id}/#{article_middle.id}"
  912. check_shown(middle: true)
  913. end
  914. it 'scrolls to bottom article ID' do
  915. visit "ticket/zoom/#{ticket.id}/#{article_top.id}"
  916. wait_for_scroll
  917. visit "ticket/zoom/#{ticket.id}/#{article_bottom.id}"
  918. check_shown(bottom: true)
  919. end
  920. end
  921. context 'when long articles are present' do
  922. it 'shows the "See more" link if you switch between the ticket and the dashboard on new articles' do
  923. ensure_websocket do
  924. # prerender ticket
  925. visit "ticket/zoom/#{ticket.id}"
  926. # ticket tab becomes background
  927. visit 'dashboard'
  928. end
  929. # create a new article
  930. article_id = create(:'ticket/article', ticket: ticket, body: "#{SecureRandom.uuid} #{"lorem ipsum\n" * 200}")
  931. wait(30).until { has_css?('div.tasks a.is-modified') }
  932. visit "ticket/zoom/#{ticket.id}"
  933. within :active_content do
  934. expect(find("div#article-content-#{article_id.id}")).to have_text('See more')
  935. end
  936. end
  937. end
  938. end
  939. describe 'Macros', authenticated_as: :authenticate do
  940. let(:macro_body) { 'macro <b>body</b>' }
  941. let(:macro) { create(:macro, perform: { 'article.note' => { 'body' => macro_body, 'internal' => 'true', 'subject' => 'macro note' } }) }
  942. let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  943. def authenticate
  944. macro
  945. true
  946. end
  947. it 'does html macro by default' do
  948. visit "ticket/zoom/#{ticket.id}"
  949. find('.js-openDropdownMacro').click
  950. find(:macro, macro.id).click
  951. expect(ticket.reload.articles.last.body).to eq(macro_body)
  952. expect(ticket.reload.articles.last.content_type).to eq('text/html')
  953. end
  954. end
  955. describe 'object manager attributes maxlength', authenticated_as: :authenticate, db_strategy: :reset do
  956. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  957. def authenticate
  958. ticket
  959. create(:object_manager_attribute_text, :required_screen, name: 'maxtest', display: 'maxtest', data_option: {
  960. 'type' => 'text',
  961. 'maxlength' => 3,
  962. 'null' => true,
  963. 'translate' => false,
  964. 'default' => '',
  965. 'options' => {},
  966. 'relation' => '',
  967. })
  968. ObjectManager::Attribute.migration_execute
  969. true
  970. end
  971. it 'checks ticket zoom' do
  972. visit "ticket/zoom/#{ticket.id}"
  973. within(:active_content) do
  974. fill_in 'maxtest', with: 'hellu'
  975. expect(page.find_field('maxtest').value).to eq('hel')
  976. end
  977. end
  978. end
  979. describe 'Update of ticket links', authenticated_as: :authenticate do
  980. let(:ticket1) { create(:ticket, group: Group.find_by(name: 'Users')) }
  981. let(:ticket2) { create(:ticket, group: Group.find_by(name: 'Users')) }
  982. def authenticate
  983. ticket1
  984. ticket2
  985. create(:link, from: ticket1, to: ticket2)
  986. true
  987. end
  988. it 'does update the state of the ticket links' do
  989. visit "ticket/zoom/#{ticket1.id}"
  990. # check title changes
  991. expect(page).to have_text(ticket2.title)
  992. ticket2.update(title: 'super new title')
  993. expect(page).to have_text(ticket2.reload.title)
  994. # check state changes
  995. expect(page).to have_css('div.links .tasks svg.open')
  996. ticket2.update(state: Ticket::State.find_by(name: 'closed'))
  997. expect(page).to have_css('div.links .tasks svg.closed')
  998. end
  999. end
  1000. describe 'GitLab Integration', :integration, authenticated_as: :authenticate, required_envs: %w[GITLAB_ENDPOINT GITLAB_APITOKEN] do
  1001. let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1002. def authenticate
  1003. Setting.set('gitlab_integration', true)
  1004. Setting.set('gitlab_config', {
  1005. api_token: ENV['GITLAB_APITOKEN'],
  1006. endpoint: ENV['GITLAB_ENDPOINT'],
  1007. })
  1008. true
  1009. end
  1010. it 'creates links and removes them' do
  1011. visit "#ticket/zoom/#{ticket.id}"
  1012. within(:active_content) do
  1013. # switch to GitLab sidebar
  1014. click('.tabsSidebar-tab[data-tab=gitlab]')
  1015. click('.sidebar-header-headline.js-headline')
  1016. # add issue
  1017. click_on 'Link issue'
  1018. fill_in 'link', with: ENV['GITLAB_ISSUE_LINK']
  1019. click_on 'Submit'
  1020. # verify issue
  1021. content = find('.sidebar-git-issue-content')
  1022. expect(content).to have_text('#1 Example issue')
  1023. expect(content).to have_text('critical')
  1024. expect(content).to have_text('special')
  1025. expect(content).to have_text('important milestone')
  1026. expect(content).to have_text('zammad-robot')
  1027. expect(ticket.reload.preferences[:gitlab][:issue_links][0]).to eq(ENV['GITLAB_ISSUE_LINK'])
  1028. # check sidebar counter increased to 1
  1029. expect(find('.tabsSidebar-tab[data-tab=gitlab] .js-tabCounter')).to have_text('1')
  1030. # delete issue
  1031. click(".sidebar-git-issue-delete span[data-issue-id='#{ENV['GITLAB_ISSUE_LINK']}']")
  1032. content = find('.sidebar[data-tab=gitlab] .sidebar-content')
  1033. expect(content).to have_text('No linked issues')
  1034. expect(ticket.reload.preferences[:gitlab][:issue_links][0]).to be_nil
  1035. # check that counter got removed
  1036. expect(page).to have_no_selector('.tabsSidebar-tab[data-tab=gitlab] .js-tabCounter')
  1037. end
  1038. end
  1039. end
  1040. describe 'GitHub Integration', :integration, authenticated_as: :authenticate, required_envs: %w[GITHUB_ENDPOINT GITHUB_APITOKEN] do
  1041. let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1042. def authenticate
  1043. Setting.set('github_integration', true)
  1044. Setting.set('github_config', {
  1045. api_token: ENV['GITHUB_APITOKEN'],
  1046. endpoint: ENV['GITHUB_ENDPOINT'],
  1047. })
  1048. true
  1049. end
  1050. it 'creates links and removes them' do
  1051. visit "#ticket/zoom/#{ticket.id}"
  1052. within(:active_content) do
  1053. # switch to GitHub sidebar
  1054. click('.tabsSidebar-tab[data-tab=github]')
  1055. click('.sidebar-header-headline.js-headline')
  1056. # add issue
  1057. click_on 'Link issue'
  1058. fill_in 'link', with: ENV['GITHUB_ISSUE_LINK']
  1059. click_on 'Submit'
  1060. # verify issue
  1061. content = find('.sidebar-git-issue-content')
  1062. expect(content).to have_text('#1575 GitHub integration')
  1063. expect(content).to have_text('enhancement')
  1064. expect(content).to have_text('integration')
  1065. expect(content).to have_text('4.0')
  1066. expect(content).to have_text('Thorsten')
  1067. expect(ticket.reload.preferences[:github][:issue_links][0]).to eq(ENV['GITHUB_ISSUE_LINK'])
  1068. # check sidebar counter increased to 1
  1069. expect(find('.tabsSidebar-tab[data-tab=github] .js-tabCounter')).to have_text('1')
  1070. # delete issue
  1071. click(".sidebar-git-issue-delete span[data-issue-id='#{ENV['GITHUB_ISSUE_LINK']}']")
  1072. content = find('.sidebar[data-tab=github] .sidebar-content')
  1073. expect(content).to have_text('No linked issues')
  1074. expect(ticket.reload.preferences[:github][:issue_links][0]).to be_nil
  1075. # check that counter got removed
  1076. expect(page).to have_no_selector('.tabsSidebar-tab[data-tab=github] .js-tabCounter')
  1077. end
  1078. end
  1079. end
  1080. describe 'Core Workflow' do
  1081. include_examples 'core workflow' do
  1082. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1083. let(:object_name) { 'Ticket' }
  1084. let(:before_it) do
  1085. lambda {
  1086. ensure_websocket(check_if_pinged: false) do
  1087. visit "#ticket/zoom/#{ticket.id}"
  1088. end
  1089. }
  1090. end
  1091. end
  1092. end
  1093. context 'Sidebar - Open & Closed Tickets', performs_jobs: true, searchindex: true do
  1094. let(:customer) { create(:customer, :with_org) }
  1095. let(:ticket_open) { create(:ticket, group: Group.find_by(name: 'Users'), customer: customer, title: SecureRandom.uuid) }
  1096. let(:ticket_closed) { create(:ticket, group: Group.find_by(name: 'Users'), customer: customer, state: Ticket::State.find_by(name: 'closed'), title: SecureRandom.uuid) }
  1097. before do
  1098. ticket_open
  1099. ticket_closed
  1100. perform_enqueued_jobs
  1101. searchindex_model_reload([Ticket, User, Organization])
  1102. end
  1103. it 'does show open and closed tickets in advanced search url' do
  1104. visit "#ticket/zoom/#{ticket_open.id}"
  1105. click '.tabsSidebar-tab[data-tab=customer]'
  1106. click '.user-tickets[data-type=open]'
  1107. expect(page).to have_text(ticket_open.title)
  1108. visit "#ticket/zoom/#{ticket_open.id}"
  1109. click '.user-tickets[data-type=closed]'
  1110. expect(page).to have_text(ticket_closed.title)
  1111. end
  1112. end
  1113. context 'Sidebar - Organization' do
  1114. let(:organization) { create(:organization) }
  1115. context 'members section' do
  1116. let(:customers) { create_list(:customer, 50, organization: organization) }
  1117. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users'), customer: customers.first) }
  1118. let(:members) { organization.members.reorder(id: :asc) }
  1119. before do
  1120. visit "#ticket/zoom/#{ticket.id}"
  1121. click '.tabsSidebar-tab[data-tab=organization]'
  1122. end
  1123. it 'shows first 10 members and loads more on demand' do
  1124. expect(page).to have_text(members[9].fullname)
  1125. expect(page).to have_no_text(members[10].fullname)
  1126. click '.js-showMoreMembers'
  1127. expect(page).to have_text(members[10].fullname)
  1128. end
  1129. end
  1130. end
  1131. describe 'merging happened in the background', authenticated_as: :user do
  1132. before do
  1133. merged_into_trigger && received_merge_trigger && update_trigger
  1134. visit "ticket/zoom/#{ticket.id}"
  1135. visit "ticket/zoom/#{target_ticket.id}"
  1136. ensure_websocket do
  1137. visit 'dashboard'
  1138. end
  1139. end
  1140. let(:merged_into_trigger) { create(:trigger, :conditionable, condition_ticket_action: :merged_into) }
  1141. let(:received_merge_trigger) { create(:trigger, :conditionable, condition_ticket_action: :received_merge) }
  1142. let(:update_trigger) { create(:trigger, :conditionable, condition_ticket_action: :update) }
  1143. let(:ticket) { create(:ticket) }
  1144. let(:target_ticket) { create(:ticket) }
  1145. let(:user) { create(:agent, :preferencable, notification_group_ids: [ticket, target_ticket].map(&:group_id), groups: [ticket, target_ticket].map(&:group)) }
  1146. context 'when merging ticket' do
  1147. before do
  1148. ticket.merge_to(ticket_id: target_ticket.id, user_id: 1)
  1149. end
  1150. it 'pulses source ticket' do
  1151. expect(page).to have_css("#navigation a.is-modified[data-key=\"Ticket-#{ticket.id}\"]")
  1152. end
  1153. it 'pulses target ticket' do
  1154. expect(page).to have_css("#navigation a.is-modified[data-key=\"Ticket-#{target_ticket.id}\"]")
  1155. end
  1156. end
  1157. context 'when merging and looking at online notifications', :performs_jobs do
  1158. before do
  1159. perform_enqueued_jobs do
  1160. ticket.merge_to(ticket_id: target_ticket.id, user_id: 1)
  1161. end
  1162. find('.js-toggleNotifications').click
  1163. end
  1164. it 'shows online notification for source ticket' do
  1165. expect(page).to have_text("Ticket #{ticket.title} was merged into another ticket")
  1166. end
  1167. it 'shows online notification for target ticket' do
  1168. expect(page).to have_text("Another ticket was merged into ticket #{ticket.title}")
  1169. end
  1170. end
  1171. end
  1172. describe 'Tab behaviour - Define default "stay on tab" / "close tab" behavior #257', authenticated_as: :authenticate do
  1173. def authenticate
  1174. Setting.set('ticket_secondary_action', 'closeTabOnTicketClose')
  1175. true
  1176. end
  1177. let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1178. before do
  1179. visit "ticket/zoom/#{ticket.id}"
  1180. end
  1181. it 'does show the default of the system' do
  1182. expect(page).to have_text('Close tab on ticket close')
  1183. end
  1184. it 'does save state for the user preferences' do
  1185. click '.js-attributeBar .dropup div'
  1186. click 'span[data-type=stayOnTab]'
  1187. refresh
  1188. expect(page).to have_text('Stay on tab')
  1189. expect(User.find_by(email: 'admin@example.com').preferences[:secondaryAction]).to eq('stayOnTab')
  1190. end
  1191. it 'does show the correct tab state after update of the ticket (#4094)' do
  1192. select 'closed', from: 'State'
  1193. click '.js-attributeBar .dropup div'
  1194. click 'span[data-type=stayOnTab]'
  1195. click '.js-submit'
  1196. expect(page.find('.js-secondaryActionButtonLabel')).to have_text('Stay on tab')
  1197. end
  1198. context 'Tab behaviour - Close tab on ticket close' do
  1199. it 'does not close the tab without any action' do
  1200. click '.js-submit'
  1201. expect(current_url).to include('ticket/zoom')
  1202. end
  1203. it 'does close the tab on ticket close' do
  1204. select 'closed', from: 'State'
  1205. click '.js-submit'
  1206. expect(current_url).not_to include('ticket/zoom')
  1207. end
  1208. end
  1209. context 'Tab behaviour - Stay on tab' do
  1210. def authenticate
  1211. Setting.set('ticket_secondary_action', 'stayOnTab')
  1212. true
  1213. end
  1214. it 'does not close the tab without any action' do
  1215. click '.js-submit'
  1216. expect(current_url).to include('ticket/zoom')
  1217. end
  1218. it 'does not close the tab on ticket close' do
  1219. select 'closed', from: 'State'
  1220. click '.js-submit'
  1221. expect(current_url).to include('ticket/zoom')
  1222. end
  1223. end
  1224. context 'Tab behaviour - Close tab' do
  1225. def authenticate
  1226. Setting.set('ticket_secondary_action', 'closeTab')
  1227. true
  1228. end
  1229. it 'does close the tab without any action' do
  1230. click '.js-submit'
  1231. expect(current_url).not_to include('ticket/zoom')
  1232. end
  1233. it 'does close the tab on ticket close' do
  1234. select 'closed', from: 'State'
  1235. click '.js-submit'
  1236. expect(current_url).not_to include('ticket/zoom')
  1237. end
  1238. end
  1239. context 'Tab behaviour - Next in overview' do
  1240. let(:ticket1) { create(:ticket, title: SecureRandom.uuid, group: Group.find_by(name: 'Users')) }
  1241. let(:ticket2) { create(:ticket, title: SecureRandom.uuid, group: Group.find_by(name: 'Users')) }
  1242. let(:ticket3) { create(:ticket, title: SecureRandom.uuid, group: Group.find_by(name: 'Users')) }
  1243. def authenticate
  1244. Setting.set('ticket_secondary_action', 'closeNextInOverview')
  1245. ticket1
  1246. ticket2
  1247. ticket3
  1248. true
  1249. end
  1250. before do
  1251. visit 'ticket/view/all_open'
  1252. end
  1253. it 'does change the tab without any action' do
  1254. click_on ticket1.title
  1255. expect(current_url).to include("ticket/zoom/#{ticket1.id}")
  1256. click '.js-submit'
  1257. expect(current_url).to include("ticket/zoom/#{ticket2.id}")
  1258. click '.js-submit'
  1259. expect(current_url).to include("ticket/zoom/#{ticket3.id}")
  1260. end
  1261. it 'does show default stay on tab if secondary action is not given' do
  1262. click_on ticket1.title
  1263. refresh
  1264. expect(page).to have_text('Stay on tab')
  1265. end
  1266. end
  1267. context 'On ticket switch' do
  1268. let(:ticket1) { create(:ticket, title: SecureRandom.uuid, group: Group.find_by(name: 'Users')) }
  1269. let(:ticket2) { create(:ticket, title: SecureRandom.uuid, group: Group.find_by(name: 'Users')) }
  1270. before do
  1271. visit "ticket/zoom/#{ticket1.id}"
  1272. visit "ticket/zoom/#{ticket2.id}"
  1273. end
  1274. it 'does setup the last behaviour' do
  1275. click '.js-attributeBar .dropup div'
  1276. click 'span[data-type=stayOnTab]'
  1277. wait.until do
  1278. User.find_by(email: 'admin@example.com').preferences['secondaryAction'] == 'stayOnTab'
  1279. end
  1280. visit "ticket/zoom/#{ticket1.id}"
  1281. expect(page).to have_text('Stay on tab')
  1282. end
  1283. end
  1284. end
  1285. describe 'Core Workflow: Show hidden attributes on group selection (ticket edit) #3739', authenticated_as: :authenticate, db_strategy: :reset do
  1286. let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1287. let(:field_name) { SecureRandom.uuid }
  1288. let(:field) do
  1289. create(:object_manager_attribute_text, name: field_name, display: field_name, screens: {
  1290. 'edit' => {
  1291. 'ticket.agent' => {
  1292. 'shown' => false,
  1293. 'required' => false,
  1294. }
  1295. }
  1296. })
  1297. ObjectManager::Attribute.migration_execute
  1298. end
  1299. before do
  1300. visit "#ticket/zoom/#{ticket.id}"
  1301. end
  1302. context 'when field visible' do
  1303. let(:workflow) do
  1304. create(:core_workflow,
  1305. object: 'Ticket',
  1306. perform: { "ticket.#{field_name}" => { 'operator' => 'show', 'show' => 'true' } })
  1307. end
  1308. def authenticate
  1309. field
  1310. workflow
  1311. true
  1312. end
  1313. it 'does show up the field' do
  1314. expect(page).to have_css("div[data-attribute-name='#{field_name}']")
  1315. end
  1316. end
  1317. context 'when field hidden' do
  1318. def authenticate
  1319. field
  1320. true
  1321. end
  1322. it 'does not show the field' do
  1323. expect(page).to have_css("div[data-attribute-name='#{field_name}'].is-hidden", visible: :hidden)
  1324. end
  1325. end
  1326. end
  1327. describe 'Notes on existing ticks are discarded by editing profile settings #3088' do
  1328. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1329. before do
  1330. visit "#ticket/zoom/#{ticket.id}"
  1331. end
  1332. def upload_and_set_text
  1333. page.find('.js-textarea').send_keys("Hello\nThis\nis\nimportant!\nyo\nhoho\ntest test test test")
  1334. page.find('input#fileUpload_1', visible: :all).set(Rails.root.join('test/data/mail/mail001.box'))
  1335. expect(page).to have_text('mail001.box')
  1336. wait_for_upload_present
  1337. end
  1338. def wait_for_upload_present
  1339. wait.until { Taskbar.find_by(key: "Ticket-#{ticket.id}").attributes_with_association_ids['attachments'].present? }
  1340. end
  1341. def wait_for_upload_blank
  1342. wait.until { Taskbar.find_by(key: "Ticket-#{ticket.id}").attributes_with_association_ids['attachments'].blank? }
  1343. end
  1344. def switch_language_german
  1345. visit '#profile/language'
  1346. # Suppress the modal dialog that invites to contributions for translations that are < 90% as this breaks the tests for de-de.
  1347. page.evaluate_script "App.LocalStorage.set('translation_support_no', true, App.Session.get('id'))"
  1348. page.find('.js-input').click
  1349. page.find('.js-input').set('Deutsch')
  1350. page.find('.js-input').send_keys(:enter)
  1351. click_on 'Submit'
  1352. visit "#ticket/zoom/#{ticket.id}"
  1353. expect(page).to have_text(Translation.translate('de-de', 'select attachment…'))
  1354. end
  1355. def expect_upload_and_text
  1356. expect(page.find('.article-new')).to have_text('mail001.box')
  1357. expect(page.find('.article-new')).to have_text("Hello\nThis\nis\nimportant!\nyo\nhoho\ntest test test test")
  1358. end
  1359. def expect_no_upload_and_text
  1360. expect(page.find('.article-new')).to have_no_text('mail001.box')
  1361. expect(page.find('.article-new')).to have_no_text("Hello\nThis\nis\nimportant!\nyo\nhoho\ntest test test test")
  1362. end
  1363. it 'does show up the attachments after a reload of the page' do
  1364. upload_and_set_text
  1365. expect_upload_and_text
  1366. refresh
  1367. expect_upload_and_text
  1368. end
  1369. it 'does show up the attachments after updating language (ui:rerender event)' do
  1370. upload_and_set_text
  1371. expect_upload_and_text
  1372. switch_language_german
  1373. expect_upload_and_text
  1374. end
  1375. it 'does remove attachments and text on reset' do
  1376. upload_and_set_text
  1377. expect_upload_and_text
  1378. page.find('.js-reset').click
  1379. wait_for_upload_blank
  1380. expect_no_upload_and_text
  1381. refresh
  1382. expect_no_upload_and_text
  1383. end
  1384. context 'when rerendering (#3831)' do
  1385. def rerender
  1386. page.evaluate_script("App.Event.trigger('ui:rerender')")
  1387. end
  1388. it 'does loose attachments after rerender' do
  1389. upload_and_set_text
  1390. expect_upload_and_text
  1391. rerender
  1392. expect_upload_and_text
  1393. end
  1394. it 'does not readd the attachments after reset' do
  1395. upload_and_set_text
  1396. expect_upload_and_text
  1397. page.find('.js-reset').click
  1398. wait_for_upload_blank
  1399. expect_no_upload_and_text
  1400. rerender
  1401. expect_no_upload_and_text
  1402. end
  1403. it 'does not readd the attachments after submit' do
  1404. upload_and_set_text
  1405. expect_upload_and_text
  1406. page.find('.js-submit').click
  1407. wait_for_upload_blank
  1408. expect_no_upload_and_text
  1409. rerender
  1410. expect_no_upload_and_text
  1411. end
  1412. it 'does not show the ticket as changed after the upload removal' do
  1413. page.find('input#fileUpload_1[data-initialized="true"]', visible: :all).set(Rails.root.join('test/data/mail/mail001.box'))
  1414. await_empty_ajax_queue
  1415. expect(page.find('.article-new')).to have_text('mail001.box')
  1416. wait_for_upload_present
  1417. begin
  1418. page.evaluate_script("$('div.attachment-delete.js-delete:last').trigger('click')") # not interactable
  1419. rescue # Lint/SuppressedException
  1420. # because its not interactable it also
  1421. # returns this weird exception for the jquery
  1422. # even tho it worked fine
  1423. end
  1424. expect(page).to have_no_selector('.js-reset')
  1425. end
  1426. end
  1427. end
  1428. describe 'Unable to close tickets in certran cases if core workflow is used #3710', authenticated_as: :authenticate, db_strategy: :reset do
  1429. let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1430. let(:field_name) { SecureRandom.uuid }
  1431. let(:field) do
  1432. create(:object_manager_attribute_text, name: field_name, display: field_name, screens: {
  1433. 'edit' => {
  1434. 'ticket.agent' => {
  1435. 'shown' => false,
  1436. 'required' => false,
  1437. }
  1438. }
  1439. })
  1440. ObjectManager::Attribute.migration_execute
  1441. end
  1442. let(:workflow) do
  1443. create(:core_workflow,
  1444. object: 'Ticket',
  1445. perform: { "ticket.#{field_name}" => { 'operator' => 'set_mandatory', 'set_mandatory' => 'true' } })
  1446. end
  1447. def authenticate
  1448. field
  1449. workflow
  1450. true
  1451. end
  1452. before do
  1453. visit "#ticket/zoom/#{ticket.id}"
  1454. end
  1455. it 'does save the ticket because the field is mandatory but hidden' do
  1456. admin = User.find_by(email: 'admin@example.com')
  1457. select admin.fullname, from: 'Owner'
  1458. find('.js-submit').click
  1459. expect(ticket.reload.owner_id).to eq(admin.id)
  1460. end
  1461. end
  1462. describe "escaped 'Set fixed' workflows don't refresh set values on active ticket sessions #3757", authenticated_as: :authenticate, db_strategy: :reset do
  1463. let(:field_name) { SecureRandom.uuid }
  1464. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users'), field_name => false) }
  1465. def authenticate
  1466. workflow
  1467. create(:object_manager_attribute_boolean, :required_screen, name: field_name, display: field_name)
  1468. ObjectManager::Attribute.migration_execute
  1469. ticket
  1470. true
  1471. end
  1472. before do
  1473. visit "#ticket/zoom/#{ticket.id}"
  1474. end
  1475. context 'when operator set_fixed_to' do
  1476. let(:workflow) do
  1477. create(:core_workflow,
  1478. object: 'Ticket',
  1479. perform: { "ticket.#{field_name}" => { 'operator' => 'set_fixed_to', 'set_fixed_to' => ['false'] } })
  1480. end
  1481. context 'when saved value is removed by set_fixed_to operator' do
  1482. it 'does show up the saved value if it would not be possible because of the restriction' do
  1483. expect(page.find("select[name='#{field_name}']").value).to eq('false')
  1484. ticket.update(field_name => true)
  1485. wait.until { page.find("select[name='#{field_name}']").value == 'true' }
  1486. expect(page.find("select[name='#{field_name}']").value).to eq('true')
  1487. end
  1488. end
  1489. end
  1490. context 'when operator remove_option' do
  1491. let(:workflow) do
  1492. create(:core_workflow,
  1493. object: 'Ticket',
  1494. perform: { "ticket.#{field_name}" => { 'operator' => 'remove_option', 'remove_option' => ['true'] } })
  1495. end
  1496. context 'when saved value is removed by set_fixed_to operator' do
  1497. it 'does show up the saved value if it would not be possible because of the restriction' do
  1498. expect(page.find("select[name='#{field_name}']").value).to eq('false')
  1499. ticket.update(field_name => true)
  1500. wait.until { page.find("select[name='#{field_name}']").value == 'true' }
  1501. expect(page.find("select[name='#{field_name}']").value).to eq('true')
  1502. end
  1503. end
  1504. end
  1505. end
  1506. context 'Basic sidebar handling because of regressions in #3757' do
  1507. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1508. before do
  1509. visit "#ticket/zoom/#{ticket.id}"
  1510. ensure_websocket
  1511. end
  1512. it 'does show up the new priority' do
  1513. high_prio = Ticket::Priority.find_by(name: '3 high')
  1514. ticket.update(priority: high_prio)
  1515. wait.until { page.find("select[name='priority_id']").value == high_prio.id.to_s }
  1516. expect(page.find("select[name='priority_id']").value).to eq(high_prio.id.to_s)
  1517. end
  1518. it 'does show up the new group (different case because it will also trigger a full rerender because of potential permission changes)' do
  1519. group = Group.find_by(name: 'some group1')
  1520. ticket.update(group: group)
  1521. wait.until { page.find("input[name='group_id']", visible: :all).value == group.id.to_s }
  1522. expect(page.find("input[name='group_id']", visible: :all).value).to eq(group.id.to_s)
  1523. end
  1524. it 'does show up the new state and pending time' do
  1525. pending_state = Ticket::State.find_by(name: 'pending reminder')
  1526. ticket.update(state: pending_state, pending_time: 1.day.from_now)
  1527. wait.until { page.find("select[name='state_id']").value == pending_state.id.to_s }
  1528. expect(page.find("select[name='state_id']").value).to eq(pending_state.id.to_s)
  1529. expect(page).to have_css("div[data-name='pending_time']")
  1530. end
  1531. it 'does merge attributes with remote priority (ajax) and local state (user)' do
  1532. select 'closed', from: 'State'
  1533. high_prio = Ticket::Priority.find_by(name: '3 high')
  1534. closed_state = Ticket::State.find_by(name: 'closed')
  1535. ticket.update(priority: high_prio)
  1536. wait.until { page.find("select[name='priority_id']").value == high_prio.id.to_s }
  1537. expect(page.find("select[name='priority_id']").value).to eq(high_prio.id.to_s)
  1538. expect(page.find("select[name='state_id']").value).to eq(closed_state.id.to_s)
  1539. end
  1540. context 'when 2 users are in 2 different tickets' do
  1541. let(:ticket2) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1542. let(:agent2) { create(:agent, password: 'test', groups: [Group.find_by(name: 'Users')]) }
  1543. before do
  1544. using_session(:second_browser) do
  1545. login(
  1546. username: agent2.login,
  1547. password: 'test',
  1548. )
  1549. visit "#ticket/zoom/#{ticket.id}"
  1550. visit "#ticket/zoom/#{ticket2.id}"
  1551. end
  1552. end
  1553. it 'does not make any changes to the second browser ticket' do
  1554. closed_state = Ticket::State.find_by(name: 'closed')
  1555. select 'closed', from: 'State'
  1556. find('.js-submit').click
  1557. using_session(:second_browser) do
  1558. sleep 3
  1559. expect(page.find("select[name='state_id']").value).not_to eq(closed_state.id.to_s)
  1560. end
  1561. end
  1562. end
  1563. end
  1564. context 'Article box opening on tickets with no changes #3789' do
  1565. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1566. before do
  1567. visit "#ticket/zoom/#{ticket.id}"
  1568. end
  1569. it 'does not expand the article box without changes' do
  1570. refresh
  1571. sleep 3
  1572. expect(page).to have_no_selector('form.article-add.is-open')
  1573. end
  1574. it 'does open and close by usage' do
  1575. find('.js-writeArea').click
  1576. find('.js-textarea').send_keys(' ')
  1577. expect(page).to have_css('form.article-add.is-open')
  1578. find('input#global-search').click
  1579. expect(page).to have_no_selector('form.article-add.is-open')
  1580. end
  1581. it 'does open automatically when body is given from sidebar' do
  1582. find('.js-textarea').send_keys('test')
  1583. wait.until { Taskbar.find_by(key: "Ticket-#{ticket.id}").state.dig('article', 'body').present? }
  1584. refresh
  1585. expect(page).to have_css('form.article-add.is-open')
  1586. end
  1587. it 'does open automatically when attachment is given from sidebar' do
  1588. page.find('input#fileUpload_1[data-initialized="true"]', visible: :all).set(Rails.root.join('test/data/mail/mail001.box'))
  1589. wait.until { Taskbar.find_by(key: "Ticket-#{ticket.id}").attributes_with_association_ids['attachments'].present? }
  1590. refresh
  1591. expect(page).to have_css('form.article-add.is-open')
  1592. end
  1593. end
  1594. context 'Owner should get cleared if not listed in changed group #3818', authenticated_as: :authenticate do
  1595. let(:group1) { create(:group) }
  1596. let(:group2) { create(:group) }
  1597. let(:agent1) { create(:agent) }
  1598. let(:agent2) { create(:agent) }
  1599. let(:ticket) { create(:ticket, group: group1, owner: agent1) }
  1600. def authenticate
  1601. agent1.group_names_access_map = {
  1602. group1.name => 'full',
  1603. group2.name => %w[read change overview]
  1604. }
  1605. agent2.group_names_access_map = {
  1606. group1.name => 'full',
  1607. group2.name => 'full',
  1608. }
  1609. agent1
  1610. end
  1611. before do
  1612. visit "#ticket/zoom/#{ticket.id}"
  1613. end
  1614. it 'does clear agent1 on select of group 2' do
  1615. set_tree_select_value('group_id', group2.name)
  1616. wait.until { page.find('select[name=owner_id]').value != agent1.id.to_s }
  1617. expect(page.find('select[name=owner_id]').value).to eq('')
  1618. expect(page.all('select[name=owner_id] option').map(&:value)).not_to include(agent1.id.to_s)
  1619. expect(page.all('select[name=owner_id] option').map(&:value)).to include(agent2.id.to_s)
  1620. end
  1621. end
  1622. describe 'Not displayed fields should not impact the edit screen #3819', authenticated_as: :authenticate, db_strategy: :reset do
  1623. let(:field_name) { SecureRandom.uuid }
  1624. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1625. def authenticate
  1626. create(:object_manager_attribute_boolean, default: nil, screens: {
  1627. edit: {
  1628. 'ticket.agent' => {
  1629. shown: false,
  1630. required: false,
  1631. }
  1632. }
  1633. })
  1634. ObjectManager::Attribute.migration_execute
  1635. ticket
  1636. true
  1637. end
  1638. before do
  1639. visit "#ticket/zoom/#{ticket.id}"
  1640. end
  1641. it 'does not show any changes for the field because it has no value and because it is not shown it should also not show the ticket as changed' do
  1642. sleep 3
  1643. expect(page).to have_no_selector('.js-reset')
  1644. end
  1645. end
  1646. describe 'Changing ticket status from "new" to any other status always results in uncommited status "closed" #3880', authenticated_as: :authenticate do
  1647. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1648. let(:workflow) do
  1649. create(:core_workflow,
  1650. object: 'Ticket',
  1651. condition_selected: {
  1652. 'ticket.priority_id': {
  1653. operator: 'is',
  1654. value: [ Ticket::Priority.find_by(name: '3 high').id.to_s ],
  1655. },
  1656. },
  1657. perform: { 'ticket.state_id' => { operator: 'remove_option', remove_option: [ Ticket::State.find_by(name: 'pending reminder').id.to_s ] } })
  1658. end
  1659. def authenticate
  1660. workflow
  1661. true
  1662. end
  1663. before do
  1664. visit "#ticket/zoom/#{ticket.id}"
  1665. end
  1666. it 'does switch back to the saved value in the ticket instead of the first value of the dropdown' do
  1667. page.select 'pending reminder', from: 'state_id'
  1668. page.select '3 high', from: 'priority_id'
  1669. expect(page).to have_select('state_id', selected: 'new')
  1670. end
  1671. end
  1672. describe 'Multiselect marked as dirty', authenticated_as: :authenticate, db_strategy: :reset do
  1673. let(:field_name) { SecureRandom.uuid }
  1674. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users'), field_name => []) }
  1675. def authenticate
  1676. create(:object_manager_attribute_multiselect, name: field_name, display: field_name, screens: {
  1677. 'edit' => {
  1678. 'ticket.agent' => {
  1679. 'shown' => true,
  1680. 'required' => false,
  1681. }
  1682. }
  1683. })
  1684. ObjectManager::Attribute.migration_execute
  1685. ticket
  1686. true
  1687. end
  1688. before do
  1689. visit "#ticket/zoom/#{ticket.id}"
  1690. end
  1691. it 'does show values properly and can save values also' do
  1692. expect(page).to have_no_css('.attributeBar-reset')
  1693. end
  1694. end
  1695. describe 'Multiselect displaying and saving', authenticated_as: :authenticate, db_strategy: :reset do
  1696. let(:field_name) { SecureRandom.uuid }
  1697. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users'), field_name => %w[value_2 value_3]) }
  1698. let(:options_hash) do
  1699. {
  1700. 'value_1' => 'value_1',
  1701. 'value_2' => 'value_2',
  1702. 'value_3' => 'value_3',
  1703. }
  1704. end
  1705. let(:data_option) { { options: options_hash, default: [] } }
  1706. def authenticate
  1707. create(:object_manager_attribute_multiselect, name: field_name, display: field_name, data_option: data_option, screens: {
  1708. 'edit' => {
  1709. 'ticket.agent' => {
  1710. 'shown' => true,
  1711. 'required' => false,
  1712. }
  1713. }
  1714. })
  1715. ObjectManager::Attribute.migration_execute
  1716. ticket
  1717. true
  1718. end
  1719. before do
  1720. visit "#ticket/zoom/#{ticket.id}"
  1721. end
  1722. def multiselect_field
  1723. page.find("select[name='#{field_name}']")
  1724. end
  1725. def multiselect_value
  1726. multiselect_field.value
  1727. end
  1728. def multiselect_set(values)
  1729. multiselect_unset_all
  1730. values = Array(values)
  1731. values.each do |value|
  1732. multiselect_field.select(value)
  1733. expect(multiselect_value).to include(value)
  1734. end
  1735. end
  1736. def multiselect_unset_all
  1737. values = multiselect_value
  1738. values.each do |value|
  1739. multiselect_field.unselect(value)
  1740. expect(multiselect_value).to not_include(value)
  1741. end
  1742. end
  1743. context 'when showing and saving values' do
  1744. it 'shows saved values properly' do
  1745. expect(multiselect_value).to eq(%w[value_2 value_3])
  1746. end
  1747. it 'saves multiple values properly' do
  1748. multiselect_set(%w[value_1 value_2])
  1749. click '.js-submit'
  1750. expect(ticket.reload[field_name]).to eq(%w[value_1 value_2])
  1751. end
  1752. it 'saves single value properly' do
  1753. multiselect_set(%w[value_1])
  1754. click '.js-submit'
  1755. expect(ticket.reload[field_name]).to eq(%w[value_1])
  1756. end
  1757. it 'removes saved values properly' do
  1758. multiselect_unset_all
  1759. click '.js-submit'
  1760. expect(ticket.reload[field_name]).to be_empty
  1761. end
  1762. end
  1763. end
  1764. describe 'Add confirmation dialog on visibility change of an article or in article creation #3924', authenticated_as: :authenticate do
  1765. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1766. let(:article) { create(:ticket_article, ticket: ticket) }
  1767. before do
  1768. visit "#ticket/zoom/#{article.ticket.id}"
  1769. end
  1770. context 'when dialog is disabled' do
  1771. def authenticate
  1772. true
  1773. end
  1774. it 'does set the article internal and external for existing articles' do
  1775. expect { page.find('.js-ArticleAction[data-type=internal]').click }.to change { article.reload.internal }.to(true)
  1776. expect { page.find('.js-ArticleAction[data-type=public]').click }.to change { article.reload.internal }.to(false)
  1777. end
  1778. it 'does set the article internal and external for new article' do
  1779. page.find('.js-writeArea').click(x: 5, y: 5)
  1780. expect(page).to have_css('.article-new .icon-internal')
  1781. expect(page).to have_no_css('.article-new .icon-public')
  1782. page.find('.article-new .icon-internal').click
  1783. expect(page).to have_no_css('.article-new .icon-internal')
  1784. expect(page).to have_css('.article-new .icon-public')
  1785. page.find('.article-new .icon-public').click
  1786. expect(page).to have_css('.article-new .icon-internal')
  1787. expect(page).to have_no_css('.article-new .icon-public')
  1788. end
  1789. end
  1790. context 'when dialog is enabled' do
  1791. def authenticate
  1792. Setting.set('ui_ticket_zoom_article_visibility_confirmation_dialog', true)
  1793. true
  1794. end
  1795. it 'does set the article internal and external for existing articles' do
  1796. expect { page.find('.js-ArticleAction[data-type=internal]').click }.to change { article.reload.internal }.to(true)
  1797. page.find('.js-ArticleAction[data-type=public]').click
  1798. in_modal do
  1799. expect { find('button[type=submit]').click }.to change { article.reload.internal }.to(false)
  1800. end
  1801. end
  1802. it 'does set the article internal and external for new article' do
  1803. page.find('.js-writeArea').click(x: 5, y: 5)
  1804. expect(page).to have_css('.article-new .icon-internal')
  1805. expect(page).to have_no_css('.article-new .icon-public')
  1806. page.find('.article-new .icon-internal').click
  1807. in_modal do
  1808. find('button[type=submit]').click
  1809. end
  1810. expect(page).to have_no_css('.article-new .icon-internal')
  1811. expect(page).to have_css('.article-new .icon-public')
  1812. page.find('.article-new .icon-public').click
  1813. expect(page).to have_css('.article-new .icon-internal')
  1814. expect(page).to have_no_css('.article-new .icon-public')
  1815. end
  1816. end
  1817. end
  1818. describe 'Show which escalation type escalated in ticket zoom #3928', authenticated_as: :authenticate do
  1819. let(:sla) { create(:sla, first_response_time: 1, update_time: 1, solution_time: 1) }
  1820. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1821. def authenticate
  1822. sla
  1823. true
  1824. end
  1825. before do
  1826. visit "#ticket/zoom/#{ticket.id}"
  1827. end
  1828. it 'does show the extended escalation information' do
  1829. page.find('.escalation-popover').hover
  1830. expect(page).to have_text('FIRST RESPONSE TIME')
  1831. expect(page).to have_text('UPDATE TIME')
  1832. expect(page).to have_text('SOLUTION TIME')
  1833. end
  1834. end
  1835. context 'Make sidebar attachments unique #3930', authenticated_as: :authenticate do
  1836. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1837. let(:article1) { create(:ticket_article, ticket: ticket) }
  1838. let(:article2) { create(:ticket_article, ticket: ticket) }
  1839. def attachment_add(article, filename)
  1840. create(:store,
  1841. object: 'Ticket::Article',
  1842. o_id: article.id,
  1843. data: "content #{filename}",
  1844. filename: filename,
  1845. preferences: {
  1846. 'Content-Type' => 'text/plain',
  1847. })
  1848. end
  1849. def authenticate
  1850. attachment_add(article1, 'some_file.txt')
  1851. attachment_add(article2, 'some_file.txt')
  1852. attachment_add(article2, 'some_file2.txt')
  1853. Setting.set('ui_ticket_zoom_sidebar_article_attachments', true)
  1854. true
  1855. end
  1856. before do
  1857. visit "#ticket/zoom/#{ticket.id}"
  1858. page.find(".tabsSidebar-tabs .tabsSidebar-tab[data-tab='attachment']").click
  1859. end
  1860. it 'does show the attachment once' do
  1861. expect(page).to have_css('.sidebar-content .attachment.attachment--preview', count: 2)
  1862. expect(page).to have_css('.sidebar-content', text: 'some_file.txt')
  1863. expect(page).to have_css('.sidebar-content', text: 'some_file2.txt')
  1864. end
  1865. it 'does show up new attachments' do
  1866. page.find('.js-textarea').send_keys('new article with attachment')
  1867. page.find('input#fileUpload_1', visible: :all).set(Rails.root.join('test/data/mail/mail001.box'))
  1868. expect(page).to have_text('mail001.box')
  1869. wait.until { Taskbar.find_by(key: "Ticket-#{ticket.id}").attributes_with_association_ids['attachments'].present? }
  1870. click '.js-submit'
  1871. expect(page).to have_css('.sidebar-content', text: 'mail001.box')
  1872. end
  1873. end
  1874. describe 'Error “customer_id required” on Macro execution #4022', authenticated_as: :authenticate do
  1875. let(:ticket) { create(:ticket, group: Group.first) }
  1876. let(:macro) { create(:macro, perform: { 'ticket.customer_id'=>{ 'pre_condition' => 'current_user.id', 'value' => nil, 'value_completion' => '' } }) }
  1877. def authenticate
  1878. ticket && macro
  1879. true
  1880. end
  1881. before do
  1882. visit "#ticket/zoom/#{ticket.id}"
  1883. end
  1884. it 'does set the agent as customer via macro' do
  1885. click '.js-openDropdownMacro'
  1886. page.find(:macro, macro.id).click
  1887. expect(ticket.reload.customer_id).to eq(User.find_by(email: 'admin@example.com').id)
  1888. end
  1889. end
  1890. context 'Assign user to multiple organizations #1573', authenticated_as: :authenticate do
  1891. let(:organizations) { create_list(:organization, 20) }
  1892. let(:customer) { create(:customer, organization: organizations[0], organizations: organizations[1..]) }
  1893. let(:ticket) { create(:ticket, group: Group.first, customer: customer) }
  1894. def authenticate
  1895. customer
  1896. true
  1897. end
  1898. before do
  1899. visit "#ticket/zoom/#{ticket.id}"
  1900. click '.tabsSidebar-tab[data-tab=customer]'
  1901. end
  1902. it 'shows only first 3 organizations and loads more on demand' do
  1903. expect(page).to have_text(organizations[1].name)
  1904. expect(page).to have_text(organizations[2].name)
  1905. expect(page).to have_no_text(organizations[10].name)
  1906. click '.js-showMoreOrganizations a'
  1907. expect(page).to have_text(organizations[10].name)
  1908. end
  1909. end
  1910. describe 'Image preview #4044' do
  1911. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1912. let(:image_as_base64) do
  1913. file = Rails.root.join('spec/fixtures/files/image/squares.png').binread
  1914. Base64.encode64(file).delete("\n")
  1915. end
  1916. let(:body) do
  1917. "<img style='width: 1004px; max-width: 100%;' src='data:image/png;base64,#{image_as_base64}'><br>"
  1918. end
  1919. let(:article) { create(:ticket_article, ticket: ticket, body: body, content_type: 'text/html') }
  1920. before do
  1921. visit "#ticket/zoom/#{article.ticket.id}"
  1922. end
  1923. it 'does open the image preview for a common image' do
  1924. within :active_ticket_article, article do
  1925. find('img').click
  1926. end
  1927. in_modal do
  1928. expect(page).to have_css('div.imagePreview img')
  1929. expect(page).to have_css('.js-cancel')
  1930. expect(page).to have_css('.js-submit')
  1931. page.find('.js-cancel').click
  1932. end
  1933. end
  1934. context 'with image and embedded link' do
  1935. let(:body) do
  1936. "<a href='https://zammad.com' title='Zammad' target='_blank'>
  1937. <img style='width: 1004px; max-width: 100%;' src='data:image/png;base64,#{image_as_base64}'>
  1938. </a><br>"
  1939. end
  1940. it 'does open the link for an image with an embedded link' do
  1941. within :active_ticket_article, article do
  1942. find('img').click
  1943. end
  1944. within_window switch_to_window_index(2) do
  1945. expect(page).to have_link(class: ['logo'])
  1946. end
  1947. close_window_index(2)
  1948. end
  1949. end
  1950. end
  1951. describe 'Copying ticket number' do
  1952. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1953. let(:ticket_number_copy_element) { 'span.ticket-number-copy svg.ticketNumberCopy-icon' }
  1954. let(:expected_clipboard_content) { (Setting.get('ticket_hook') + ticket.number).to_s }
  1955. let(:field) { find(:richtext) }
  1956. before do
  1957. visit "#ticket/zoom/#{ticket.id}"
  1958. end
  1959. it 'copies the ticket number correctly' do
  1960. find(ticket_number_copy_element).click
  1961. # simulate a paste action
  1962. within(:active_content) do
  1963. field.send_keys('')
  1964. field.click
  1965. field.send_keys([magic_key, 'v'])
  1966. end
  1967. expect(field.text).to eq(expected_clipboard_content)
  1968. end
  1969. end
  1970. describe 'Allow additional usage of Ticket Number in (Zoom) URL #849' do
  1971. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1972. it 'does find the ticket by ticket number' do
  1973. visit "#ticket/zoom/number/#{ticket.number}"
  1974. expect(current_url).to include("ticket/zoom/#{ticket.id}")
  1975. end
  1976. it 'does fail properly for ticket numbers which are not found' do
  1977. visit '#ticket/zoom/number/123456789'
  1978. expect(page).to have_text("I can't find this Ticket")
  1979. end
  1980. end
  1981. describe 'Article update causes missing icons in the UI after switching internal state #4213' do
  1982. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1983. let(:article) { create(:ticket_article, ticket: ticket, body: SecureRandom.uuid) }
  1984. before do
  1985. visit "#ticket/zoom/#{article.ticket.id}"
  1986. end
  1987. it 'does find the ticket by ticket number' do
  1988. expect(page).to have_text(article.body)
  1989. article.update(body: SecureRandom.uuid)
  1990. expect(page).to have_text(article.body)
  1991. click '.js-ArticleAction[data-type=internal]'
  1992. click '.js-ArticleAction[data-type=public]'
  1993. expect(page).to have_css('.js-ArticleAction[data-type=emailReply]')
  1994. end
  1995. end
  1996. describe 'Open ticket indicator coloring setting' do
  1997. let(:elem) { find('[data-tab="customer"]') }
  1998. let(:customer) { create(:customer) }
  1999. let(:group) { Group.first }
  2000. let(:ticket) { create(:ticket, customer: customer, group: group) }
  2001. before do
  2002. Setting.set 'ui_sidebar_open_ticket_indicator_colored', state
  2003. customer.update! preferences: { tickets_open: tickets_count }
  2004. visit "ticket/zoom/#{ticket.id}"
  2005. end
  2006. context 'when enabled' do
  2007. let(:state) { true }
  2008. context 'with 1 ticket' do
  2009. let(:tickets_count) { 1 }
  2010. it 'does not highlight' do
  2011. expect(elem)
  2012. .to have_no_selector('.tabsSidebar-tab-count--danger, .tabsSidebar-tab-count--warning')
  2013. end
  2014. end
  2015. context 'with 2 tickets' do
  2016. let(:tickets_count) { 2 }
  2017. it 'highlights as warning' do
  2018. create(:ticket, customer: customer, group: group)
  2019. expect(elem)
  2020. .to have_no_selector('.tabsSidebar-tab-count--danger')
  2021. .and have_css('.tabsSidebar-tab-count--warning')
  2022. end
  2023. end
  2024. context 'with 3 tickets' do
  2025. let(:tickets_count) { 3 }
  2026. it 'highlights as danger' do
  2027. expect(elem)
  2028. .to have_css('.tabsSidebar-tab-count--danger')
  2029. .and have_no_selector('.tabsSidebar-tab-count--warning')
  2030. end
  2031. end
  2032. end
  2033. context 'when disabled' do
  2034. let(:state) { false }
  2035. context 'with 2 tickets' do
  2036. let(:tickets_count) { 2 }
  2037. it 'does not highlight' do
  2038. expect(elem)
  2039. .to have_no_selector('.tabsSidebar-tab-count--danger, .tabsSidebar-tab-count--warning')
  2040. end
  2041. end
  2042. end
  2043. end
  2044. describe 'Incorrect notification when closing a tab after setting up an object #2042', db_strategy: :reset do
  2045. let(:ticket) { create(:ticket, group: Group.first) }
  2046. before do
  2047. # Create test ticket before adding the test object attribute.
  2048. ticket
  2049. create(:object_manager_attribute_text, name: 'text_test', display: 'text_test', screens: {
  2050. 'edit' => {
  2051. 'ticket.agent' => {
  2052. 'shown' => true,
  2053. 'required' => true,
  2054. }
  2055. }
  2056. })
  2057. ObjectManager::Attribute.migration_execute
  2058. visit "#ticket/zoom/#{ticket.id}"
  2059. end
  2060. it 'does not show the discard button nor the confirmation dialog' do
  2061. within(:active_content) do
  2062. expect(page).to have_no_css('.js-reset')
  2063. end
  2064. taskbar_tab_close("Ticket-#{ticket.id}", discard_changes: false)
  2065. expect(page).to have_no_css('.modal')
  2066. end
  2067. end
  2068. end