zoom_spec.rb 96 KB

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