create_spec.rb 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. require 'system/apps/mobile_old/examples/create_article_examples'
  4. require 'system/apps/mobile_old/examples/article_security_examples'
  5. RSpec.describe 'Mobile > Ticket > Article > Create', app: :mobile, authenticated_as: :agent, type: :system do
  6. let(:group) { Group.find_by(name: 'Users') }
  7. let(:agent) { create(:agent, groups: [group]) }
  8. let(:customer) { create(:customer) }
  9. let(:ticket) { create(:ticket, customer: customer, group: group, owner: agent) }
  10. def wait_for_ticket_edit(number: 1)
  11. wait_for_mutation('ticketUpdate', number: number)
  12. end
  13. def save_article(number: 1)
  14. find_button('Done').click
  15. wait_for_test_flag('ticket-article-reply.closed')
  16. find_button('Save').click
  17. wait_for_ticket_edit(number: number)
  18. end
  19. def open_article_dialog
  20. visit "/tickets/#{ticket.id}"
  21. wait_for_form_to_settle('form-ticket-edit')
  22. find_button('Add reply').click
  23. end
  24. context 'when creating a new article as an agent', authenticated_as: :agent do
  25. it 'disables the done button when the form is not dirty' do
  26. open_article_dialog
  27. expect(find_button('Done', disabled: true).disabled?).to be(true)
  28. end
  29. it 'enables the done button when the form is dirty' do
  30. open_article_dialog
  31. find_editor('Text').type('foobar')
  32. expect(find_button('Done').disabled?).to be(false)
  33. end
  34. it 'creates an internal note (default)' do
  35. open_article_dialog
  36. expect(find_select('Channel', visible: :all)).to have_selected_option('Note')
  37. expect(find_select('Visibility', visible: :all)).to have_selected_option('Internal')
  38. text = find_editor('Text')
  39. expect(text).to have_text_value('', exact: true)
  40. text.type('This is a note')
  41. save_article
  42. expect(Ticket::Article.last).to have_attributes(
  43. type_id: Ticket::Article::Type.lookup(name: 'note').id,
  44. internal: true,
  45. content_type: 'text/html',
  46. sender: Ticket::Article::Sender.lookup(name: 'Agent'),
  47. body: '<p>This is a note</p>',
  48. )
  49. end
  50. it 'doesn\'t show "save" button when part of ticket is changed and article is added' do
  51. visit "/tickets/#{ticket.id}/information"
  52. wait_for_form_to_settle('form-ticket-edit')
  53. find_input('Ticket title').type('foobar')
  54. click_on('Add reply')
  55. expect(find_select('Channel', visible: :all)).to have_selected_option('Note')
  56. expect(find_select('Visibility', visible: :all)).to have_selected_option('Internal')
  57. text = find_editor('Text')
  58. expect(text).to have_text_value('', exact: true)
  59. text.type('This is a note')
  60. save_article
  61. expect(page).to have_no_button('Save')
  62. end
  63. it 'creates a public note' do
  64. open_article_dialog
  65. find_select('Visibility', visible: :all).select_option('Public')
  66. text = find_editor('Text')
  67. expect(text).to have_text_value('', exact: true)
  68. text.type('This is a note!')
  69. save_article
  70. expect(Ticket::Article.last).to have_attributes(
  71. type_id: Ticket::Article::Type.lookup(name: 'note').id,
  72. internal: false,
  73. content_type: 'text/html',
  74. body: '<p>This is a note!</p>',
  75. )
  76. end
  77. context 'when creating an email' do
  78. let(:signature) { create(:signature, active: true, body: "\#{user.firstname}<br>Signature!") }
  79. let(:group) { create(:group, signature: signature) }
  80. it 'creates a public email (default)' do
  81. visit "/tickets/#{ticket.id}"
  82. find_button('Add reply').click
  83. find_select('Channel', visible: :all).select_option('Email')
  84. wait_for_test_flag('editor.signatureAdd')
  85. find_editor('Text').type('This is a note!')
  86. find_autocomplete('To').search_for_option('zammad_test_to@zammad.com', gql_number: 1)
  87. find_autocomplete('CC').search_for_option('zammad_test_cc@zammad.com', gql_number: 2)
  88. find_button('Save').click
  89. wait_for_ticket_edit
  90. expect(Ticket::Article.last).to have_attributes(
  91. type_id: Ticket::Article::Type.lookup(name: 'email').id,
  92. to: 'zammad_test_to@zammad.com',
  93. cc: 'zammad_test_cc@zammad.com',
  94. internal: false,
  95. content_type: 'text/html',
  96. body: "<p>This is a note!</p><p><br></p><div data-signature=\"true\" data-signature-id=\"#{signature.id}\"><p>#{agent.firstname}<br>Signature!</p></div>",
  97. )
  98. end
  99. it 'creates an internal email' do
  100. visit "/tickets/#{ticket.id}"
  101. find_button('Add reply').click
  102. find_select('Channel', visible: :all).select_option('Email')
  103. wait_for_test_flag('editor.signatureAdd')
  104. find_editor('Text').type('This is a note!')
  105. find_autocomplete('To').search_for_option('zammad_test_to@zammad.com', gql_number: 1)
  106. visibility = find_select('Visibility', visible: :all)
  107. expect(visibility).to have_selected_option('Public')
  108. visibility.select_option('Internal')
  109. find_button('Save').click
  110. wait_for_ticket_edit
  111. expect(Ticket::Article.last).to have_attributes(
  112. type_id: Ticket::Article::Type.lookup(name: 'email').id,
  113. internal: true,
  114. content_type: 'text/html',
  115. body: "<p>This is a note!</p><p><br></p><div data-signature=\"true\" data-signature-id=\"#{signature.id}\"><p>#{agent.firstname}<br>Signature!</p></div>",
  116. )
  117. end
  118. end
  119. context 'when an article was just deleted', current_user_id: -> { agent.id } do
  120. def delete_article(article_body, number: 1)
  121. wait_for_subscription_start('ticketArticleUpdates')
  122. within '[role="comment"]', text: article_body do
  123. find('[data-name="article-context"]').click
  124. end
  125. click_on 'Delete Article'
  126. click_on 'OK'
  127. wait_for_subscription_update('ticketArticleUpdates', number: number)
  128. end
  129. def create_article(article_body, number: 1)
  130. find_button('Add reply').click
  131. wait_for_test_flag('ticket-article-reply.opened')
  132. within_form(form_updater_gql_number: number) do
  133. text = find_editor('Text')
  134. expect(text).to have_text_value('', exact: true)
  135. text.type(article_body)
  136. expect(text).to have_text_value(article_body)
  137. end
  138. save_article(number: number)
  139. end
  140. context 'when deleting the first/last article' do
  141. it 'shows the correct articles' do
  142. visit "/tickets/#{ticket.id}"
  143. wait_for_form_to_settle('form-ticket-edit')
  144. create_article('Article 1')
  145. delete_article('Article 1')
  146. create_article('This is a new note', number: 2)
  147. expect(page).to have_no_text('Article 1')
  148. .and have_text('This is a new note')
  149. end
  150. end
  151. context 'when deleting an article in the middle' do
  152. it 'shows the correct articles' do
  153. visit "/tickets/#{ticket.id}"
  154. wait_for_form_to_settle('form-ticket-edit')
  155. create_article('Article 1')
  156. create_article('Article 2', number: 2)
  157. create_article('Article 3', number: 3)
  158. delete_article('Article 2')
  159. create_article('This is a new note', number: 4)
  160. expect(page).to have_text('Article 1')
  161. .and have_no_text('Article 2')
  162. .and have_text('This is a new note')
  163. .and have_text('Article 3')
  164. end
  165. end
  166. end
  167. it 'changes ticket data together with the article' do
  168. open_article_dialog
  169. find_editor('Text').type('This is a note!')
  170. # close reply dialog
  171. find_button('Done').click
  172. # go to the ticket edit view
  173. find_link(ticket.title).click
  174. find_input('Ticket title').type('New title')
  175. find_button('Save').click
  176. wait_for_ticket_edit
  177. expect(ticket.reload.title).to eq('New title')
  178. expect(Ticket::Article.last).to have_attributes(
  179. type_id: Ticket::Article::Type.lookup(name: 'note').id,
  180. content_type: 'text/html',
  181. body: '<p>This is a note!</p>',
  182. )
  183. end
  184. context 'when creating a phone article' do
  185. include_examples 'mobile app: create article', 'Phone', attachments: true, conditional: false do
  186. let(:article) { create(:ticket_article, :outbound_phone, ticket: ticket) }
  187. let(:type) { Ticket::Article::Type.lookup(name: 'phone') }
  188. let(:content_type) { 'text/html' }
  189. end
  190. end
  191. context 'when creating sms article' do
  192. include_examples 'mobile app: create article', 'Sms', conditional: true do
  193. let(:article) do
  194. create(
  195. :ticket_article,
  196. ticket: ticket,
  197. type: Ticket::Article::Type.find_by(name: 'sms'),
  198. )
  199. end
  200. let(:type) { Ticket::Article::Type.lookup(name: 'sms') }
  201. let(:content_type) { 'text/plain' }
  202. end
  203. end
  204. context 'when creating telegram article' do
  205. include_examples 'mobile app: create article', 'Telegram', attachments: true do
  206. let(:article) do
  207. create(
  208. :ticket_article,
  209. ticket: ticket,
  210. type: Ticket::Article::Type.find_by(name: 'telegram personal-message'),
  211. )
  212. end
  213. let(:type) { Ticket::Article::Type.lookup(name: 'telegram personal-message') }
  214. let(:content_type) { 'text/plain' }
  215. end
  216. end
  217. context 'when replying to twitter status ticket' do
  218. include_examples 'mobile app: create article', 'Twitter', attachments: false do
  219. let(:article) do
  220. create(
  221. :twitter_article,
  222. ticket: ticket,
  223. sender: Ticket::Article::Sender.lookup(name: 'Customer'),
  224. )
  225. end
  226. let(:type) { Ticket::Article::Type.lookup(name: 'twitter status') }
  227. let(:content_type) { 'text/plain' }
  228. let(:result_text) { "#{new_text}\n/#{agent.firstname.first}#{agent.lastname.first}" }
  229. end
  230. end
  231. context 'when replying to twitter dm ticket' do
  232. include_examples 'mobile app: create article', 'Twitter', attachments: false do
  233. let(:article) do
  234. create(
  235. :twitter_dm_article,
  236. ticket: ticket,
  237. sender: Ticket::Article::Sender.lookup(name: 'Customer'),
  238. )
  239. end
  240. let(:type) { Ticket::Article::Type.lookup(name: 'twitter direct-message') }
  241. let(:content_type) { 'text/plain' }
  242. let(:to) { article.from }
  243. let(:result_text) { "#{new_text}\n/#{agent.firstname.first}#{agent.lastname.first}" }
  244. end
  245. end
  246. context 'when replying to a facebook post' do
  247. include_examples 'mobile app: create article', 'Facebook', attachments: false do
  248. let(:article) do
  249. create(
  250. :ticket_article,
  251. ticket: ticket,
  252. sender: Ticket::Article::Sender.lookup(name: 'Customer'),
  253. type: Ticket::Article::Type.lookup(name: 'facebook feed post'),
  254. )
  255. end
  256. let(:type) { Ticket::Article::Type.lookup(name: 'facebook feed comment') }
  257. let(:content_type) { 'text/plain' }
  258. end
  259. end
  260. context 'when using suggestions' do
  261. let(:text_option) do
  262. content = "Hello, \#{ticket.customer.firstname}!"
  263. content += " Ticket \#{ticket.title} has group \#{ticket.group.name}."
  264. create(
  265. :text_module,
  266. name: 'test',
  267. content: content
  268. )
  269. end
  270. it 'text suggestion parses correctly' do
  271. create(:ticket_article, ticket: ticket)
  272. open_article_dialog
  273. find_editor('Text').type('::test')
  274. find('[role="option"]', text: text_option.name).click
  275. body = "Hello, #{ticket.customer.firstname}!"
  276. body += " Ticket #{ticket.title} has group #{ticket.group.name}."
  277. expect(find_editor('Text')).to have_text(body)
  278. end
  279. end
  280. # TODO: test security settings
  281. end
  282. context 'when creating a new article as a customer', authenticated_as: :customer do
  283. it 'creates an article with web type' do
  284. open_article_dialog
  285. text = find_editor('Text')
  286. expect(text).to have_text_value('', exact: true)
  287. text.type('This is a note')
  288. save_article
  289. expect(Ticket::Article.last).to have_attributes(
  290. type_id: Ticket::Article::Type.lookup(name: 'web').id,
  291. internal: false,
  292. content_type: 'text/html',
  293. sender: Ticket::Article::Sender.lookup(name: 'Customer'),
  294. body: '<p>This is a note</p>',
  295. )
  296. end
  297. end
  298. context 'when creating secured article', authenticated_as: :authenticate do
  299. def prepare_phone_article
  300. open_article_dialog
  301. find_select('Channel', visible: :all).select_option('Phone')
  302. end
  303. def prepare_email_article(with_body: false)
  304. open_article_dialog
  305. find_select('Channel', visible: :all).select_option('Email')
  306. find_autocomplete('To').search_for_option(customer.email, label: customer.fullname)
  307. find_editor('Text').type(Faker::Hacker.say_something_smart) if with_body
  308. end
  309. def submit_form
  310. save_article
  311. end
  312. it_behaves_like 'mobile app: article security', integration: :smime
  313. it_behaves_like 'mobile app: article security', integration: :pgp
  314. end
  315. context 'when inlining an image', authenticated_as: :agent do
  316. it 'correctly compresses image' do
  317. open_article_dialog
  318. find_editor('Text').type(Faker::Hacker.say_something_smart)
  319. click_on('Add image') # inserts an invisible input
  320. find('[data-test-id="editor-image-input"]', visible: :all).attach_file(Rails.root.join('spec/fixtures/files/image/large2.png'))
  321. wait_for_test_flag('editor.imageResized')
  322. save_article
  323. # The fize will always be less than it actually is even without resizing
  324. # Chrome has the best compression, so we check that actual value is lower than Firefox's compresssion
  325. expect(Store.last.size.to_i).to be <= 25_686
  326. end
  327. end
  328. end