From 4cf30166afaefe3d981987432fe3d326d5b6e9cf Mon Sep 17 00:00:00 2001 From: as-op Date: Tue, 25 Jul 2023 16:38:21 +0200 Subject: [PATCH 01/14] [#49081] Add tests for pdf content https://community.openproject.org/work_packages/49081 --- Gemfile | 3 + Gemfile.lock | 13 ++ .../work_package_list_to_pdf_spec.rb | 220 ++++++++++++++++++ .../pdf_export/work_package_to_pdf_spec.rb | 96 ++++++++ 4 files changed, 332 insertions(+) create mode 100644 spec/workers/work_packages/pdf_export/work_package_list_to_pdf_spec.rb create mode 100644 spec/workers/work_packages/pdf_export/work_package_to_pdf_spec.rb diff --git a/Gemfile b/Gemfile index 904dbb8c2408..e17090087897 100644 --- a/Gemfile +++ b/Gemfile @@ -237,6 +237,9 @@ group :test do # XML comparison tests gem 'compare-xml', '~> 0.66', require: false + # PDF Export tests + gem 'pdf-inspector', '~> 1.2' + # brings back testing for 'assigns' and 'assert_template' extracted in rails 5 gem 'rails-controller-testing', '~> 1.0.2' diff --git a/Gemfile.lock b/Gemfile.lock index 563f6a50e3f5..4d6f78468544 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -205,6 +205,7 @@ PATH GEM remote: https://rubygems.org/ specs: + Ascii85 (1.1.0) actioncable (7.0.6) actionpack (= 7.0.6) activesupport (= 7.0.6) @@ -294,6 +295,7 @@ GEM addressable (2.8.4) public_suffix (>= 2.0.2, < 6.0) aes_key_wrap (1.1.0) + afm (0.2.2) airbrake (13.0.3) airbrake-ruby (~> 6.0) airbrake-ruby (6.2.1) @@ -518,6 +520,7 @@ GEM gravatar_image_tag (1.2.0) hana (1.3.7) hashdiff (1.0.1) + hashery (2.1.2) hashie (3.6.0) html-pipeline (2.14.3) activesupport (>= 2) @@ -661,6 +664,14 @@ GEM ast (~> 2.4.1) racc pdf-core (0.9.0) + pdf-inspector (1.3.0) + pdf-reader (>= 1.0, < 3.0.a) + pdf-reader (2.11.0) + Ascii85 (~> 1.0) + afm (~> 0.2.1) + hashery (~> 2.0) + ruby-rc4 + ttfunk pg (1.5.3) plaintext (0.3.4) activesupport (> 2.2.1) @@ -848,6 +859,7 @@ GEM ruby-ole (1.2.12.2) ruby-prof (1.6.3) ruby-progressbar (1.13.0) + ruby-rc4 (0.1.5) ruby-saml (1.15.0) nokogiri (>= 1.13.10) rexml @@ -1084,6 +1096,7 @@ DEPENDENCIES ox paper_trail (~> 12.3) parallel_tests (~> 4.0) + pdf-inspector (~> 1.2) pg (~> 1.5.0) plaintext (~> 0.3.2) posix-spawn (~> 0.3.13) diff --git a/spec/workers/work_packages/pdf_export/work_package_list_to_pdf_spec.rb b/spec/workers/work_packages/pdf_export/work_package_list_to_pdf_spec.rb new file mode 100644 index 000000000000..4f1389d8269d --- /dev/null +++ b/spec/workers/work_packages/pdf_export/work_package_list_to_pdf_spec.rb @@ -0,0 +1,220 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2023 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +require 'spec_helper' +require 'pdf/inspector' + +RSpec.describe WorkPackage::PDFExport::WorkPackageListToPdf do + include Redmine::I18n + let(:type1) { create(:type_standard) } + let(:type2) { create(:type_bug) } + let(:project) { create(:project, name: 'Foo Bla. Report No. 4/2021 with/for Case 42', types: [type1, type2]) } + let(:user) do + create(:user, + member_in_project: project, + member_with_permissions: %w[view_work_packages export_work_packages]) + end + let(:column_names) { [:id, :subject, :status, :story_points] } + + def work_package_columns(wp) + [wp.id.to_s, wp.subject, wp.status.name, wp.story_points.to_s] + end + + def work_package_details(wp, nr) + ["#{nr}.", wp.subject, + 'ID', wp.id.to_s, + 'STATUS', wp.status.name, + 'STORY POINTS', wp.story_points.to_s, + 'Description', + wp.description] + end + + let(:export_time) { + DateTime.new(2023, 6, 30, 23, 59, 00, '+00:00') + } + let(:export_time_formatted) { + format_time(export_time, true) + } + + let(:work_package1) do + create(:work_package, + project:, + type: type1, + subject: 'Work package 1', + story_points: 1, + description: 'This is a description' + ) + end + let(:work_package2) do + create(:work_package, project:, + type: type2, + subject: 'Work package 2', + story_points: 2, + description: 'This is work package 2' + ) + end + let(:work_packages) do + [work_package1, work_package2] + end + let(:query_attributes) { {} } + let!(:query) do + build(:query, user:, project:, **query_attributes).tap do |q| + q.column_names = column_names + q.sort_criteria = [%w[id asc]] + end + end + let(:column_titles) { column_names.map { |name| WorkPackage.human_attribute_name(name).upcase } } + let(:options) { {} } + + let(:export) do + login_as(user) + work_packages + described_class.new(query, options) + end + + let(:export_pdf) do + Timecop.freeze(export_time) do + export.export! + end + end + + def open_temp_pdf + cmd = "open -a Preview #{export_pdf.content.path}" + puts cmd + `#{cmd}` + end + + subject(:pdf) do + PDF::Inspector::Text.analyze(File.read(export_pdf.content.path)) + end + + describe 'with a request for a PDF table' do + it 'contains correct data' do + expect(pdf.strings).to eq([ + query.name, + *column_titles, + *work_package_columns(work_package1), + *work_package_columns(work_package2), + '1/1', export_time_formatted, query.name + ]) + end + end + + describe 'with a request for a PDF table grouped' do + let(:query_attributes) { { group_by: 'type' } } + + it 'contains correct data' do + expect(pdf.strings).to eq([ + query.name, + type1.name, + *column_titles, + *work_package_columns(work_package1), + type2.name, + *column_titles, + *work_package_columns(work_package2), + '1/1', export_time_formatted, query.name + ]) + end + end + + describe 'with a request for a PDF table grouped with sums' do + let(:query_attributes) { { group_by: 'type', display_sums: true } } + + it 'contains correct data' do + expect(pdf.strings).to eq([ + query.name, + type1.name, + *column_titles, + *work_package_columns(work_package1), + "Sum", work_package1.story_points.to_s, + type2.name, + *column_titles, + *work_package_columns(work_package2), + "Sum", work_package2.story_points.to_s, + '1/1', export_time_formatted, query.name + ]) + end + end + + describe 'with a request for a PDF Report' do + let(:options) { { show_report: true } } + + it 'contains correct data' do + expect(pdf.strings).to eq([ + query.name, + '1.', '2', work_package1.subject, + '2.', '2', work_package2.subject, + '1/2', export_time_formatted, query.name, + *work_package_details(work_package1, 1), + *work_package_details(work_package2, 2), + '2/2', export_time_formatted, query.name + ]) + end + end + + describe 'with a request for a PDF Report with sums' do + let(:options) { { show_report: true } } + let(:query_attributes) { { display_sums: true } } + + it 'contains correct data' do + expect(pdf.strings).to eq([ + query.name, + '1.', '2', work_package1.subject, + '2.', '2', work_package2.subject, + '1/2', export_time_formatted, query.name, + "Overview", + "STORY POINTS", "Sum", (work_package1.story_points + work_package2.story_points).to_s, + *work_package_details(work_package1, 1), + *work_package_details(work_package2, 2), + '2/2', export_time_formatted, query.name + ]) + end + end + + describe 'with a request for a PDF Report grouped with sums' do + let(:options) { { show_report: true } } + let(:query_attributes) { { display_sums: true, group_by: 'type' } } + + it 'contains correct data' do + expect(pdf.strings).to eq([ + query.name, + '1.', '2', work_package1.subject, + '2.', '2', work_package2.subject, + '1/2', export_time_formatted, query.name, + "Overview", + "TYPE", "STORY POINTS", + type1.name, work_package1.story_points.to_s, + type2.name, work_package2.story_points.to_s, + "Sum", (work_package1.story_points + work_package2.story_points).to_s, + *work_package_details(work_package1, 1), + *work_package_details(work_package2, 2), + '2/2', export_time_formatted, query.name + ]) + end + end +end diff --git a/spec/workers/work_packages/pdf_export/work_package_to_pdf_spec.rb b/spec/workers/work_packages/pdf_export/work_package_to_pdf_spec.rb new file mode 100644 index 000000000000..9806a54f02a1 --- /dev/null +++ b/spec/workers/work_packages/pdf_export/work_package_to_pdf_spec.rb @@ -0,0 +1,96 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2023 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +require 'spec_helper' +require 'pdf/inspector' + +RSpec.describe WorkPackage::PDFExport::WorkPackageToPdf do + include Redmine::I18n + let(:type) { create(:type_bug) } + let(:project) { create(:project, name: 'Foo Bla. Report No. 4/2021 with/for Case 42', types: [type]) } + let(:user) do + create(:user, + member_in_project: project, + member_with_permissions: %w[view_work_packages export_work_packages]) + end + let(:export_time) { + DateTime.new(2023, 6, 30, 23, 59, 00, '+00:00') + } + let(:export_time_formatted) { + format_time(export_time, true) + } + + let(:wp) do + create(:work_package, + project:, + type:, + subject: 'Work package 1', + story_points: 1, + description: 'This is a description' + ) + end + let(:options) { {} } + + let(:export) do + login_as(user) + described_class.new(wp, options) + end + + let(:export_pdf) do + Timecop.freeze(export_time) do + export.export! + end + end + + subject(:pdf) do + PDF::Inspector::Text.analyze(export_pdf.content) + end + + describe 'with a request for a PDF' do + it 'contains correct data' do + expect(pdf.strings).to eq([ + "#{type.name} ##{wp.id} - #{wp.subject}", + 'ID', wp.id.to_s, + "UPDATED ON", export_time_formatted, + "TYPE", type.name, + "CREATED ON", export_time_formatted, + 'STATUS', wp.status.name, + "FINISH DATE", + "VERSION", + "PRIORITY", wp.priority.name, + "DURATION", + "WORK", + "CATEGORY", + "ASSIGNEE", + 'Description', + wp.description, + '1', export_time_formatted, project.name + ]) + end + end +end From c114081c171e269d3669864330313b18b5f2eb8a Mon Sep 17 00:00:00 2001 From: as-op Date: Tue, 25 Jul 2023 16:41:28 +0200 Subject: [PATCH 02/14] Query.available_columns containing duplicates --- app/models/query.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/query.rb b/app/models/query.rb index 8c20401bc18b..b1c7fffbdebf 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -224,6 +224,7 @@ def self.available_columns(project = nil) .columns[self] .map { |col| col.instances(project) } .flatten + .uniq(&:name) end def self.displayable_columns From f52ed8b29d5b9b928059f79869c6b5a46e3a3c3f Mon Sep 17 00:00:00 2001 From: as-op Date: Tue, 25 Jul 2023 17:04:18 +0200 Subject: [PATCH 03/14] fix(rubocop): obey linting --- .../work_package_list_to_pdf_spec.rb | 141 +++++++++--------- .../pdf_export/work_package_to_pdf_spec.rb | 25 ++-- 2 files changed, 81 insertions(+), 85 deletions(-) diff --git a/spec/workers/work_packages/pdf_export/work_package_list_to_pdf_spec.rb b/spec/workers/work_packages/pdf_export/work_package_list_to_pdf_spec.rb index 4f1389d8269d..491bae370392 100644 --- a/spec/workers/work_packages/pdf_export/work_package_list_to_pdf_spec.rb +++ b/spec/workers/work_packages/pdf_export/work_package_list_to_pdf_spec.rb @@ -31,55 +31,35 @@ RSpec.describe WorkPackage::PDFExport::WorkPackageListToPdf do include Redmine::I18n - let(:type1) { create(:type_standard) } - let(:type2) { create(:type_bug) } - let(:project) { create(:project, name: 'Foo Bla. Report No. 4/2021 with/for Case 42', types: [type1, type2]) } + let(:type_standard) { create(:type_standard) } + let(:type_bug) { create(:type_bug) } + let(:project) { create(:project, name: 'Foo Bla. Report No. 4/2021 with/for Case 42', types: [type_standard, type_bug]) } let(:user) do create(:user, member_in_project: project, member_with_permissions: %w[view_work_packages export_work_packages]) end - let(:column_names) { [:id, :subject, :status, :story_points] } - - def work_package_columns(wp) - [wp.id.to_s, wp.subject, wp.status.name, wp.story_points.to_s] - end - - def work_package_details(wp, nr) - ["#{nr}.", wp.subject, - 'ID', wp.id.to_s, - 'STATUS', wp.status.name, - 'STORY POINTS', wp.story_points.to_s, - 'Description', - wp.description] - end - - let(:export_time) { - DateTime.new(2023, 6, 30, 23, 59, 00, '+00:00') - } - let(:export_time_formatted) { - format_time(export_time, true) - } - - let(:work_package1) do + let(:export_time) { DateTime.new(2023, 6, 30, 23, 59) } + let(:export_time_formatted) { format_time(export_time, true) } + let(:work_package_parent) do create(:work_package, project:, - type: type1, + type: type_standard, subject: 'Work package 1', story_points: 1, - description: 'This is a description' - ) + description: 'This is a description') end - let(:work_package2) do - create(:work_package, project:, - type: type2, + let(:work_package_child) do + create(:work_package, + project:, + parent: work_package_parent, + type: type_bug, subject: 'Work package 2', story_points: 2, - description: 'This is work package 2' - ) + description: 'This is work package 2') end let(:work_packages) do - [work_package1, work_package2] + [work_package_parent, work_package_child] end let(:query_attributes) { {} } let!(:query) do @@ -90,23 +70,29 @@ def work_package_details(wp, nr) end let(:column_titles) { column_names.map { |name| WorkPackage.human_attribute_name(name).upcase } } let(:options) { {} } - let(:export) do login_as(user) work_packages described_class.new(query, options) end - let(:export_pdf) do Timecop.freeze(export_time) do export.export! end end + let(:column_names) { %i[id subject status story_points] } - def open_temp_pdf - cmd = "open -a Preview #{export_pdf.content.path}" - puts cmd - `#{cmd}` + def work_package_columns(work_package) + [work_package.id.to_s, work_package.subject, work_package.status.name, work_package.story_points.to_s] + end + + def work_package_details(work_package, index) + ["#{index}.", work_package.subject, + 'ID', work_package.id.to_s, + 'STATUS', work_package.status.name, + 'STORY POINTS', work_package.story_points.to_s, + 'Description', + work_package.description] end subject(:pdf) do @@ -118,8 +104,8 @@ def open_temp_pdf expect(pdf.strings).to eq([ query.name, *column_titles, - *work_package_columns(work_package1), - *work_package_columns(work_package2), + *work_package_columns(work_package_parent), + *work_package_columns(work_package_child), '1/1', export_time_formatted, query.name ]) end @@ -131,12 +117,12 @@ def open_temp_pdf it 'contains correct data' do expect(pdf.strings).to eq([ query.name, - type1.name, + work_package_parent.type.name, *column_titles, - *work_package_columns(work_package1), - type2.name, + *work_package_columns(work_package_parent), + work_package_child.type.name, *column_titles, - *work_package_columns(work_package2), + *work_package_columns(work_package_child), '1/1', export_time_formatted, query.name ]) end @@ -148,14 +134,14 @@ def open_temp_pdf it 'contains correct data' do expect(pdf.strings).to eq([ query.name, - type1.name, + work_package_parent.type.name, *column_titles, - *work_package_columns(work_package1), - "Sum", work_package1.story_points.to_s, - type2.name, + *work_package_columns(work_package_parent), + "Sum", work_package_parent.story_points.to_s, + work_package_child.type.name, *column_titles, - *work_package_columns(work_package2), - "Sum", work_package2.story_points.to_s, + *work_package_columns(work_package_child), + "Sum", work_package_child.story_points.to_s, '1/1', export_time_formatted, query.name ]) end @@ -167,11 +153,28 @@ def open_temp_pdf it 'contains correct data' do expect(pdf.strings).to eq([ query.name, - '1.', '2', work_package1.subject, - '2.', '2', work_package2.subject, + '1.', '2', work_package_parent.subject, + '2.', '2', work_package_child.subject, + '1/2', export_time_formatted, query.name, + *work_package_details(work_package_parent, 1), + *work_package_details(work_package_child, 2), + '2/2', export_time_formatted, query.name + ]) + end + end + + describe 'with a request for a PDF Report with hierarchies' do + let(:options) { { show_report: true } } + let(:query_attributes) { { show_hierarchies: true } } + + it 'contains correct data' do + expect(pdf.strings).to eq([ + query.name, + '1.', '2', work_package_parent.subject, + '1.1.', '2', work_package_child.subject, '1/2', export_time_formatted, query.name, - *work_package_details(work_package1, 1), - *work_package_details(work_package2, 2), + *work_package_details(work_package_parent, '1'), + *work_package_details(work_package_child, '1.1'), '2/2', export_time_formatted, query.name ]) end @@ -184,13 +187,13 @@ def open_temp_pdf it 'contains correct data' do expect(pdf.strings).to eq([ query.name, - '1.', '2', work_package1.subject, - '2.', '2', work_package2.subject, + '1.', '2', work_package_parent.subject, + '2.', '2', work_package_child.subject, '1/2', export_time_formatted, query.name, "Overview", - "STORY POINTS", "Sum", (work_package1.story_points + work_package2.story_points).to_s, - *work_package_details(work_package1, 1), - *work_package_details(work_package2, 2), + "STORY POINTS", "Sum", (work_package_parent.story_points + work_package_child.story_points).to_s, + *work_package_details(work_package_parent, 1), + *work_package_details(work_package_child, 2), '2/2', export_time_formatted, query.name ]) end @@ -203,16 +206,16 @@ def open_temp_pdf it 'contains correct data' do expect(pdf.strings).to eq([ query.name, - '1.', '2', work_package1.subject, - '2.', '2', work_package2.subject, + '1.', '2', work_package_parent.subject, + '2.', '2', work_package_child.subject, '1/2', export_time_formatted, query.name, "Overview", "TYPE", "STORY POINTS", - type1.name, work_package1.story_points.to_s, - type2.name, work_package2.story_points.to_s, - "Sum", (work_package1.story_points + work_package2.story_points).to_s, - *work_package_details(work_package1, 1), - *work_package_details(work_package2, 2), + work_package_parent.type.name, work_package_parent.story_points.to_s, + work_package_child.type.name, work_package_child.story_points.to_s, + "Sum", (work_package_parent.story_points + work_package_child.story_points).to_s, + *work_package_details(work_package_parent, 1), + *work_package_details(work_package_child, 2), '2/2', export_time_formatted, query.name ]) end diff --git a/spec/workers/work_packages/pdf_export/work_package_to_pdf_spec.rb b/spec/workers/work_packages/pdf_export/work_package_to_pdf_spec.rb index 9806a54f02a1..fb43a0e3dcbb 100644 --- a/spec/workers/work_packages/pdf_export/work_package_to_pdf_spec.rb +++ b/spec/workers/work_packages/pdf_export/work_package_to_pdf_spec.rb @@ -38,14 +38,9 @@ member_in_project: project, member_with_permissions: %w[view_work_packages export_work_packages]) end - let(:export_time) { - DateTime.new(2023, 6, 30, 23, 59, 00, '+00:00') - } - let(:export_time_formatted) { - format_time(export_time, true) - } - - let(:wp) do + let(:export_time) { DateTime.new(2023, 6, 30, 23, 59) } + let(:export_time_formatted) { format_time(export_time, true) } + let(:work_package) do create(:work_package, project:, type:, @@ -55,12 +50,10 @@ ) end let(:options) { {} } - let(:export) do login_as(user) - described_class.new(wp, options) + described_class.new(work_package, options) end - let(:export_pdf) do Timecop.freeze(export_time) do export.export! @@ -74,21 +67,21 @@ describe 'with a request for a PDF' do it 'contains correct data' do expect(pdf.strings).to eq([ - "#{type.name} ##{wp.id} - #{wp.subject}", - 'ID', wp.id.to_s, + "#{type.name} ##{work_package.id} - #{work_package.subject}", + 'ID', work_package.id.to_s, "UPDATED ON", export_time_formatted, "TYPE", type.name, "CREATED ON", export_time_formatted, - 'STATUS', wp.status.name, + 'STATUS', work_package.status.name, "FINISH DATE", "VERSION", - "PRIORITY", wp.priority.name, + "PRIORITY", work_package.priority.name, "DURATION", "WORK", "CATEGORY", "ASSIGNEE", 'Description', - wp.description, + work_package.description, '1', export_time_formatted, project.name ]) end From d937212f663f493f34fcd6da64715fe4a3b759cd Mon Sep 17 00:00:00 2001 From: as-op Date: Tue, 25 Jul 2023 17:06:57 +0200 Subject: [PATCH 04/14] fix(rubocop): obey linting --- .../work_packages/pdf_export/work_package_list_to_pdf_spec.rb | 3 ++- .../work_packages/pdf_export/work_package_to_pdf_spec.rb | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/workers/work_packages/pdf_export/work_package_list_to_pdf_spec.rb b/spec/workers/work_packages/pdf_export/work_package_list_to_pdf_spec.rb index 491bae370392..c3081ccd7128 100644 --- a/spec/workers/work_packages/pdf_export/work_package_list_to_pdf_spec.rb +++ b/spec/workers/work_packages/pdf_export/work_package_list_to_pdf_spec.rb @@ -191,7 +191,8 @@ def work_package_details(work_package, index) '2.', '2', work_package_child.subject, '1/2', export_time_formatted, query.name, "Overview", - "STORY POINTS", "Sum", (work_package_parent.story_points + work_package_child.story_points).to_s, + "STORY POINTS", + "Sum", (work_package_parent.story_points + work_package_child.story_points).to_s, *work_package_details(work_package_parent, 1), *work_package_details(work_package_child, 2), '2/2', export_time_formatted, query.name diff --git a/spec/workers/work_packages/pdf_export/work_package_to_pdf_spec.rb b/spec/workers/work_packages/pdf_export/work_package_to_pdf_spec.rb index fb43a0e3dcbb..1447a4a688cd 100644 --- a/spec/workers/work_packages/pdf_export/work_package_to_pdf_spec.rb +++ b/spec/workers/work_packages/pdf_export/work_package_to_pdf_spec.rb @@ -46,8 +46,7 @@ type:, subject: 'Work package 1', story_points: 1, - description: 'This is a description' - ) + description: 'This is a description') end let(:options) { {} } let(:export) do From c9bfad3bed786c454a265a0f840594a96ce7ba1a Mon Sep 17 00:00:00 2001 From: as-op Date: Wed, 26 Jul 2023 15:33:26 +0200 Subject: [PATCH 05/14] move tests to reflect source class & module position --- .../work_packages/pdf_export/work_package_list_to_pdf_spec.rb | 4 ++-- .../work_packages/pdf_export/work_package_to_pdf_spec.rb | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename spec/{workers => models}/work_packages/pdf_export/work_package_list_to_pdf_spec.rb (99%) rename spec/{workers => models}/work_packages/pdf_export/work_package_to_pdf_spec.rb (100%) diff --git a/spec/workers/work_packages/pdf_export/work_package_list_to_pdf_spec.rb b/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb similarity index 99% rename from spec/workers/work_packages/pdf_export/work_package_list_to_pdf_spec.rb rename to spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb index c3081ccd7128..b62e44679956 100644 --- a/spec/workers/work_packages/pdf_export/work_package_list_to_pdf_spec.rb +++ b/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb @@ -156,8 +156,8 @@ def work_package_details(work_package, index) '1.', '2', work_package_parent.subject, '2.', '2', work_package_child.subject, '1/2', export_time_formatted, query.name, - *work_package_details(work_package_parent, 1), - *work_package_details(work_package_child, 2), + *work_package_details(work_package_parent, "1"), + *work_package_details(work_package_child, "2"), '2/2', export_time_formatted, query.name ]) end diff --git a/spec/workers/work_packages/pdf_export/work_package_to_pdf_spec.rb b/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb similarity index 100% rename from spec/workers/work_packages/pdf_export/work_package_to_pdf_spec.rb rename to spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb From b38a1fb63f22103ef68adbf868f88263e8c9d037 Mon Sep 17 00:00:00 2001 From: as-op Date: Wed, 26 Jul 2023 16:51:42 +0200 Subject: [PATCH 06/14] add formatting. mention and image attachment to description --- .../pdf_export/work_package_to_pdf_spec.rb | 54 +++++++++++++------ 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb b/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb index 1447a4a688cd..aeb478464c98 100644 --- a/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb +++ b/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb @@ -40,13 +40,33 @@ end let(:export_time) { DateTime.new(2023, 6, 30, 23, 59) } let(:export_time_formatted) { format_time(export_time, true) } + let(:image_path) { Rails.root.join("spec/fixtures/files/image.png") } + let(:image_attachment) { Attachment.new author: user, file: File.open(image_path) } + let(:attachments) { [image_attachment] } let(:work_package) do + description = <<~DESCRIPTION + **Lorem** _ipsum_ ~~dolor~~ `sit` [amet](https://example.com/), consetetur sadipscing elitr. + @OpenProject Admin + ![](/api/v3/attachments/#{image_attachment.id}/content) +

+

+
+ "foobar" +
+
Image Caption
+
+

+ DESCRIPTION create(:work_package, - project:, - type:, - subject: 'Work package 1', - story_points: 1, - description: 'This is a description') + project:, + type:, + subject: 'Work package 1', + story_points: 1, + description:).tap do |wp| + allow(wp) + .to receive(:attachments) + .and_return attachments + end end let(:options) { {} } let(:export) do @@ -68,19 +88,21 @@ expect(pdf.strings).to eq([ "#{type.name} ##{work_package.id} - #{work_package.subject}", 'ID', work_package.id.to_s, - "UPDATED ON", export_time_formatted, - "TYPE", type.name, - "CREATED ON", export_time_formatted, + 'UPDATED ON', export_time_formatted, + 'TYPE', type.name, + 'CREATED ON', export_time_formatted, 'STATUS', work_package.status.name, - "FINISH DATE", - "VERSION", - "PRIORITY", work_package.priority.name, - "DURATION", - "WORK", - "CATEGORY", - "ASSIGNEE", + 'FINISH DATE', + 'VERSION', + 'PRIORITY', work_package.priority.name, + 'DURATION', + 'WORK', + 'CATEGORY', + 'ASSIGNEE', 'Description', - work_package.description, + 'Lorem', ' ', 'ipsum', ' ', 'dolor', ' ', 'sit', ' ', + 'amet', ', consetetur sadipscing elitr.', ' ', '@OpenProject Admin', + 'Image Caption', '1', export_time_formatted, project.name ]) end From 369fd831ef76408de2c72e65917e44f6a316d072 Mon Sep 17 00:00:00 2001 From: as-op Date: Wed, 26 Jul 2023 16:55:17 +0200 Subject: [PATCH 07/14] fix(rubocop): obey linting --- .../pdf_export/work_package_to_pdf_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb b/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb index aeb478464c98..1062ab97c92b 100644 --- a/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb +++ b/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb @@ -58,11 +58,11 @@

DESCRIPTION create(:work_package, - project:, - type:, - subject: 'Work package 1', - story_points: 1, - description:).tap do |wp| + project:, + type:, + subject: 'Work package 1', + story_points: 1, + description:).tap do |wp| allow(wp) .to receive(:attachments) .and_return attachments From c5d840fee5fb3864f9ffd9398841c7cdaedbd1b2 Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 27 Jul 2023 09:52:48 +0200 Subject: [PATCH 08/14] test(pdf-export): remove filtered out work package attributes from tests, do not hardcode column names --- .../work_package_list_to_pdf_spec.rb | 35 +++++++++++-------- .../pdf_export/work_package_to_pdf_spec.rb | 29 ++++++++------- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb b/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb index b62e44679956..2d1c22fd36fe 100644 --- a/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb +++ b/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb @@ -68,7 +68,7 @@ q.sort_criteria = [%w[id asc]] end end - let(:column_titles) { column_names.map { |name| WorkPackage.human_attribute_name(name).upcase } } + let(:column_titles) { column_names.map { |name| column_title(name) } } let(:options) { {} } let(:export) do login_as(user) @@ -82,17 +82,24 @@ end let(:column_names) { %i[id subject status story_points] } + def column_title(column_name) + label_title(column_name).upcase + end + + def label_title(column_name) + WorkPackage.human_attribute_name(column_name) + end + def work_package_columns(work_package) [work_package.id.to_s, work_package.subject, work_package.status.name, work_package.story_points.to_s] end def work_package_details(work_package, index) ["#{index}.", work_package.subject, - 'ID', work_package.id.to_s, - 'STATUS', work_package.status.name, - 'STORY POINTS', work_package.story_points.to_s, - 'Description', - work_package.description] + column_title(:id), work_package.id.to_s, + column_title(:status), work_package.status.name, + column_title(:story_points), work_package.story_points.to_s, + label_title(:description), work_package.description] end subject(:pdf) do @@ -137,11 +144,11 @@ def work_package_details(work_package, index) work_package_parent.type.name, *column_titles, *work_package_columns(work_package_parent), - "Sum", work_package_parent.story_points.to_s, + I18n.t('js.label_sum'), work_package_parent.story_points.to_s, work_package_child.type.name, *column_titles, *work_package_columns(work_package_child), - "Sum", work_package_child.story_points.to_s, + I18n.t('js.label_sum'), work_package_child.story_points.to_s, '1/1', export_time_formatted, query.name ]) end @@ -190,9 +197,9 @@ def work_package_details(work_package, index) '1.', '2', work_package_parent.subject, '2.', '2', work_package_child.subject, '1/2', export_time_formatted, query.name, - "Overview", - "STORY POINTS", - "Sum", (work_package_parent.story_points + work_package_child.story_points).to_s, + I18n.t('js.work_packages.tabs.overview'), + column_title(:story_points), + I18n.t('js.label_sum'), (work_package_parent.story_points + work_package_child.story_points).to_s, *work_package_details(work_package_parent, 1), *work_package_details(work_package_child, 2), '2/2', export_time_formatted, query.name @@ -210,11 +217,11 @@ def work_package_details(work_package, index) '1.', '2', work_package_parent.subject, '2.', '2', work_package_child.subject, '1/2', export_time_formatted, query.name, - "Overview", - "TYPE", "STORY POINTS", + I18n.t('js.work_packages.tabs.overview'), + column_title(:type), column_title(:story_points), work_package_parent.type.name, work_package_parent.story_points.to_s, work_package_child.type.name, work_package_child.story_points.to_s, - "Sum", (work_package_parent.story_points + work_package_child.story_points).to_s, + I18n.t('js.label_sum'), (work_package_parent.story_points + work_package_child.story_points).to_s, *work_package_details(work_package_parent, 1), *work_package_details(work_package_child, 2), '2/2', export_time_formatted, query.name diff --git a/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb b/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb index 1062ab97c92b..110a080256ba 100644 --- a/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb +++ b/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb @@ -79,7 +79,16 @@ end end + def column_title(column_name) + label_title(column_name).upcase + end + + def label_title(column_name) + WorkPackage.human_attribute_name(column_name) + end + subject(:pdf) do + # File.binwrite('WorkPackageToPdf-test-preview.pdf', export_pdf.content) PDF::Inspector::Text.analyze(export_pdf.content) end @@ -87,19 +96,13 @@ it 'contains correct data' do expect(pdf.strings).to eq([ "#{type.name} ##{work_package.id} - #{work_package.subject}", - 'ID', work_package.id.to_s, - 'UPDATED ON', export_time_formatted, - 'TYPE', type.name, - 'CREATED ON', export_time_formatted, - 'STATUS', work_package.status.name, - 'FINISH DATE', - 'VERSION', - 'PRIORITY', work_package.priority.name, - 'DURATION', - 'WORK', - 'CATEGORY', - 'ASSIGNEE', - 'Description', + column_title(:id), work_package.id.to_s, + column_title(:updated_at), export_time_formatted, + column_title(:type), type.name, + column_title(:created_at), export_time_formatted, + column_title(:status), work_package.status.name, + column_title(:priority), work_package.priority.name, + label_title(:description), 'Lorem', ' ', 'ipsum', ' ', 'dolor', ' ', 'sit', ' ', 'amet', ', consetetur sadipscing elitr.', ' ', '@OpenProject Admin', 'Image Caption', From 1442e31ce4465aab761a0de37aacbe04b731f2fd Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 27 Jul 2023 09:58:56 +0200 Subject: [PATCH 09/14] fix(rubocop): obey linting --- .../pdf_export/work_package_list_to_pdf_spec.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb b/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb index 2d1c22fd36fe..39bf27472289 100644 --- a/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb +++ b/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb @@ -90,6 +90,10 @@ def label_title(column_name) WorkPackage.human_attribute_name(column_name) end + def work_packages_sum + work_package_parent.story_points + work_package_child.story_points + end + def work_package_columns(work_package) [work_package.id.to_s, work_package.subject, work_package.status.name, work_package.story_points.to_s] end @@ -199,7 +203,7 @@ def work_package_details(work_package, index) '1/2', export_time_formatted, query.name, I18n.t('js.work_packages.tabs.overview'), column_title(:story_points), - I18n.t('js.label_sum'), (work_package_parent.story_points + work_package_child.story_points).to_s, + I18n.t('js.label_sum'), work_packages_sum.to_s, *work_package_details(work_package_parent, 1), *work_package_details(work_package_child, 2), '2/2', export_time_formatted, query.name @@ -221,7 +225,7 @@ def work_package_details(work_package, index) column_title(:type), column_title(:story_points), work_package_parent.type.name, work_package_parent.story_points.to_s, work_package_child.type.name, work_package_child.story_points.to_s, - I18n.t('js.label_sum'), (work_package_parent.story_points + work_package_child.story_points).to_s, + I18n.t('js.label_sum'), work_packages_sum.to_s, *work_package_details(work_package_parent, 1), *work_package_details(work_package_child, 2), '2/2', export_time_formatted, query.name From ec39889bc34898b0131f517dc3ab0b7f645b2c02 Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 27 Jul 2023 10:31:48 +0200 Subject: [PATCH 10/14] remove leftover debug helper --- app/models/query.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/query.rb b/app/models/query.rb index b1c7fffbdebf..8c20401bc18b 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -224,7 +224,6 @@ def self.available_columns(project = nil) .columns[self] .map { |col| col.instances(project) } .flatten - .uniq(&:name) end def self.displayable_columns From 7e4aa0ff1f5ffbc44ebb3d806945bfeef59a9bba Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 27 Jul 2023 11:16:59 +0200 Subject: [PATCH 11/14] consistent parameter type --- .../pdf_export/work_package_list_to_pdf_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb b/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb index 39bf27472289..005f5df891c3 100644 --- a/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb +++ b/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb @@ -204,8 +204,8 @@ def work_package_details(work_package, index) I18n.t('js.work_packages.tabs.overview'), column_title(:story_points), I18n.t('js.label_sum'), work_packages_sum.to_s, - *work_package_details(work_package_parent, 1), - *work_package_details(work_package_child, 2), + *work_package_details(work_package_parent, "1"), + *work_package_details(work_package_child, "2"), '2/2', export_time_formatted, query.name ]) end @@ -226,8 +226,8 @@ def work_package_details(work_package, index) work_package_parent.type.name, work_package_parent.story_points.to_s, work_package_child.type.name, work_package_child.story_points.to_s, I18n.t('js.label_sum'), work_packages_sum.to_s, - *work_package_details(work_package_parent, 1), - *work_package_details(work_package_child, 2), + *work_package_details(work_package_parent, "1"), + *work_package_details(work_package_child, "2"), '2/2', export_time_formatted, query.name ]) end From 5b4a4ac8a5842007b8793169232ea391551a14f9 Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 27 Jul 2023 12:23:40 +0200 Subject: [PATCH 12/14] test for exported images --- .../pdf_export/work_package_to_pdf_spec.rb | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb b/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb index 110a080256ba..c132b7eaca34 100644 --- a/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb +++ b/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb @@ -88,26 +88,31 @@ def label_title(column_name) end subject(:pdf) do - # File.binwrite('WorkPackageToPdf-test-preview.pdf', export_pdf.content) - PDF::Inspector::Text.analyze(export_pdf.content) + content = export_pdf.content + # File.binwrite('WorkPackageToPdf-test-preview.pdf', content) + { strings: PDF::Inspector::Text.analyze(content).strings, + images: PDF::Inspector::XObject.analyze(content).page_xobjects.flat_map do |o| + o.values.select { |v| v.hash[:Subtype] == :Image } + end } end describe 'with a request for a PDF' do it 'contains correct data' do - expect(pdf.strings).to eq([ - "#{type.name} ##{work_package.id} - #{work_package.subject}", - column_title(:id), work_package.id.to_s, - column_title(:updated_at), export_time_formatted, - column_title(:type), type.name, - column_title(:created_at), export_time_formatted, - column_title(:status), work_package.status.name, - column_title(:priority), work_package.priority.name, - label_title(:description), - 'Lorem', ' ', 'ipsum', ' ', 'dolor', ' ', 'sit', ' ', - 'amet', ', consetetur sadipscing elitr.', ' ', '@OpenProject Admin', - 'Image Caption', - '1', export_time_formatted, project.name - ]) + expect(pdf[:strings]).to eq([ + "#{type.name} ##{work_package.id} - #{work_package.subject}", + column_title(:id), work_package.id.to_s, + column_title(:updated_at), export_time_formatted, + column_title(:type), type.name, + column_title(:created_at), export_time_formatted, + column_title(:status), work_package.status.name, + column_title(:priority), work_package.priority.name, + label_title(:description), + 'Lorem', ' ', 'ipsum', ' ', 'dolor', ' ', 'sit', ' ', + 'amet', ', consetetur sadipscing elitr.', ' ', '@OpenProject Admin', + 'Image Caption', + '1', export_time_formatted, project.name + ]) + expect(pdf[:images].length).to eq(2) end end end From 808b05dec29d39d3e5fd9f1d4b39d3cedf7d85eb Mon Sep 17 00:00:00 2001 From: as-op Date: Thu, 27 Jul 2023 16:25:11 +0200 Subject: [PATCH 13/14] test(markdown/html): check unknown/ignored html-tag rendering --- .../models/work_packages/pdf_export/work_package_to_pdf_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb b/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb index c132b7eaca34..05915e1e1049 100644 --- a/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb +++ b/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb @@ -56,6 +56,7 @@
Image Caption

+

Foo

DESCRIPTION create(:work_package, project:, @@ -110,6 +111,7 @@ def label_title(column_name) 'Lorem', ' ', 'ipsum', ' ', 'dolor', ' ', 'sit', ' ', 'amet', ', consetetur sadipscing elitr.', ' ', '@OpenProject Admin', 'Image Caption', + 'Foo', '1', export_time_formatted, project.name ]) expect(pdf[:images].length).to eq(2) From 3a504ffbb493151485f1597bae5af85d32b75f08 Mon Sep 17 00:00:00 2001 From: as-op Date: Mon, 31 Jul 2023 10:15:43 +0200 Subject: [PATCH 14/14] via feedback: move equal methods into spec/support --- .../pdf_export/work_package_list_to_pdf_spec.rb | 10 +--------- .../pdf_export/work_package_to_pdf_spec.rb | 10 +--------- spec/support/work_packages/pdf_export.rb | 12 ++++++++++++ 3 files changed, 14 insertions(+), 18 deletions(-) create mode 100644 spec/support/work_packages/pdf_export.rb diff --git a/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb b/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb index 005f5df891c3..8c4c5e5275f3 100644 --- a/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb +++ b/spec/models/work_packages/pdf_export/work_package_list_to_pdf_spec.rb @@ -27,10 +27,10 @@ #++ require 'spec_helper' -require 'pdf/inspector' RSpec.describe WorkPackage::PDFExport::WorkPackageListToPdf do include Redmine::I18n + include PDFExportSpecUtils let(:type_standard) { create(:type_standard) } let(:type_bug) { create(:type_bug) } let(:project) { create(:project, name: 'Foo Bla. Report No. 4/2021 with/for Case 42', types: [type_standard, type_bug]) } @@ -82,14 +82,6 @@ end let(:column_names) { %i[id subject status story_points] } - def column_title(column_name) - label_title(column_name).upcase - end - - def label_title(column_name) - WorkPackage.human_attribute_name(column_name) - end - def work_packages_sum work_package_parent.story_points + work_package_child.story_points end diff --git a/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb b/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb index 05915e1e1049..15541fb64398 100644 --- a/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb +++ b/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb @@ -27,10 +27,10 @@ #++ require 'spec_helper' -require 'pdf/inspector' RSpec.describe WorkPackage::PDFExport::WorkPackageToPdf do include Redmine::I18n + include PDFExportSpecUtils let(:type) { create(:type_bug) } let(:project) { create(:project, name: 'Foo Bla. Report No. 4/2021 with/for Case 42', types: [type]) } let(:user) do @@ -80,14 +80,6 @@ end end - def column_title(column_name) - label_title(column_name).upcase - end - - def label_title(column_name) - WorkPackage.human_attribute_name(column_name) - end - subject(:pdf) do content = export_pdf.content # File.binwrite('WorkPackageToPdf-test-preview.pdf', content) diff --git a/spec/support/work_packages/pdf_export.rb b/spec/support/work_packages/pdf_export.rb new file mode 100644 index 000000000000..2d7c266098b1 --- /dev/null +++ b/spec/support/work_packages/pdf_export.rb @@ -0,0 +1,12 @@ +require 'spec_helper' +require 'pdf/inspector' + +module PDFExportSpecUtils + def column_title(column_name) + label_title(column_name).upcase + end + + def label_title(column_name) + WorkPackage.human_attribute_name(column_name) + end +end