From 806cd7def68a2145da3fc2401905ef27770c6b55 Mon Sep 17 00:00:00 2001 From: Mantas Masalskis Date: Thu, 13 Aug 2020 11:17:14 +0200 Subject: [PATCH] Fixes #3093 - Inline images are cut during forward and reply (quotation) --- Gemfile | 3 + Gemfile.lock | 2 + .../javascripts/app/lib/app_post/utils.coffee | 13 ++-- spec/fixtures/image/squares.png | Bin 0 -> 22198 bytes spec/system/ticket/zoom_spec.rb | 65 ++++++++++++++++++ 5 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 spec/fixtures/image/squares.png diff --git a/Gemfile b/Gemfile index db01175f8..4b71c7913 100644 --- a/Gemfile +++ b/Gemfile @@ -203,6 +203,9 @@ group :development, :test do # handle deprecations in core and addons gem 'deprecation_toolkit' + + # image comparison in tests + gem 'chunky_png' end # Want to extend Zammad with additional gems? diff --git a/Gemfile.lock b/Gemfile.lock index d615376bf..86315cd9f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -132,6 +132,7 @@ GEM xpath (~> 3.2) childprocess (1.0.1) rake (< 13.0) + chunky_png (1.3.12) clavius (1.0.4) clearbit (0.2.8) nestful (~> 1.1.0) @@ -585,6 +586,7 @@ DEPENDENCIES browser byebug capybara + chunky_png clearbit coffee-rails coffee-script-source diff --git a/app/assets/javascripts/app/lib/app_post/utils.coffee b/app/assets/javascripts/app/lib/app_post/utils.coffee index 632cce11f..7df426a16 100644 --- a/app/assets/javascripts/app/lib/app_post/utils.coffee +++ b/app/assets/javascripts/app/lib/app_post/utils.coffee @@ -1222,7 +1222,7 @@ class App.Utils canvas.width = img.width canvas.height = img.height ctx = canvas.getContext('2d') - ctx.drawImage(img, 0, 0) + ctx.drawImage(img, 0, 0, img.width, img.height) try data = canvas.toDataURL('image/png') params.success(img, data) if params.success @@ -1242,12 +1242,11 @@ class App.Utils return if !src? or src.match(/^(data|cid):/i) App.Utils._htmlImage2DataUrlAsync(@, success: (img, data) -> - $img = $(img) - $img.attr('src', data) - $img.css('max-width','100%') - params.success(img, data) if params.success + element.attr('src', data) + element.css('max-width','100%') + params.success(element, data) if params.success fail: (img) -> - img.remove() + element.remove() params.fail(img) if params.fail ) ) @@ -1283,7 +1282,7 @@ class App.Utils imageCache = new Image() imageCache.crossOrigin = 'anonymous' imageCache.onload = -> - App.Utils._htmlImage2DataUrl(originalImage, params) + App.Utils._htmlImage2DataUrl(imageCache, params) imageCache.onerror = -> App.Log.notice('Utils', "Unable to load image from #{originalImage.src}") params.fail(originalImage) if params.fail diff --git a/spec/fixtures/image/squares.png b/spec/fixtures/image/squares.png new file mode 100644 index 0000000000000000000000000000000000000000..a6e6ba9926d4c3cea22c185226527583efb38ea3 GIT binary patch literal 22198 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 literal 0 HcmV?d00001 diff --git a/spec/system/ticket/zoom_spec.rb b/spec/system/ticket/zoom_spec.rb index 319ea0f05..d237e644e 100644 --- a/spec/system/ticket/zoom_spec.rb +++ b/spec/system/ticket/zoom_spec.rb @@ -812,4 +812,69 @@ RSpec.describe 'Ticket zoom', type: :system do include_examples 'verify linking' end end + + describe 'forwarding article with an image' do + let(:ticket_article_body) do + filename = 'squares.png' + file = File.binread(Rails.root.join("spec/fixtures/image/#{filename}")) + ext = File.extname(filename)[1...] + base64 = Base64.encode64(file).delete("\n") + + "
" + end + + def current_ticket + Ticket.find current_url.split('/').last + end + + def create_ticket + visit '#ticket/create' + + within :active_content do + find('[data-type=email-out]').click + + find('[name=title]').fill_in with: 'Title' + find('[name=customer_id_completion]').fill_in with: 'customer@example.com' + find('[name=group_id]').select 'Users' + find(:richtext).execute_script "this.innerHTML = \"#{ticket_article_body}\"" + find('.js-submit').click + end + + await_empty_ajax_queue + end + + def forward + within :active_content do + click '.js-ArticleAction[data-type=emailForward]' + fill_in 'To', with: 'customer@example.com' + find('.js-submit').click + end + + await_empty_ajax_queue + end + + def images_identical?(image_a, image_b) + return false if image_a.height != image_b.height + return false if image_a.width != image_b.width + + image_a.height.times do |y| + image_a.row(y).each_with_index do |pixel, x| + return false unless pixel == image_b[x, y] + end + end + + true + end + + it 'keeps image intact' do + create_ticket + forward + + images = current_ticket.articles.map do |article| + ChunkyPNG::Image.from_string article.attachments.first.content + end + + expect(images_identical?(images.first, images.second)).to be(true) + end + end end