zoom_spec.rb 91 KB

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