zoom_spec.rb 96 KB

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