diff --git a/Gemfile b/Gemfile
index eb885674f97..2b4eb3a91cc 100644
--- a/Gemfile
+++ b/Gemfile
@@ -204,6 +204,9 @@ group :test do
gem 'retriable', '~> 3.1.1'
gem 'rspec-retry', '~> 0.6.1'
+ # XML comparison tests
+ gem 'compare-xml', '~> 0.66', require: false
+
gem 'rspec-example_disabler', git: 'https://github.com/finnlabs/rspec-example_disabler.git'
# brings back testing for 'assigns' and 'assert_template' extracted in rails 5
diff --git a/Gemfile.lock b/Gemfile.lock
index b76f6a35a4f..1325583f8d0 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -397,6 +397,8 @@ GEM
colored2 (3.1.2)
commonmarker (0.20.1)
ruby-enum (~> 0.5)
+ compare-xml (0.66)
+ nokogiri (~> 1.8)
concurrent-ruby (1.1.5)
cork (0.3.0)
colored2 (~> 3.1)
@@ -969,6 +971,7 @@ DEPENDENCIES
cells-erb (~> 0.1.0)
cells-rails (~> 0.0.9)
commonmarker (~> 0.20.1)
+ compare-xml (~> 0.66)
cucumber (~> 3.1.0)
cucumber-rails (~> 1.8.0)
daemons
diff --git a/modules/bcf/app/models/bcf/issue.rb b/modules/bcf/app/models/bcf/issue.rb
index 0bb8c0b4dd5..4dee1c6f3bc 100644
--- a/modules/bcf/app/models/bcf/issue.rb
+++ b/modules/bcf/app/models/bcf/issue.rb
@@ -19,6 +19,10 @@ module Bcf
end
end
+ def imported_title
+ markup_doc.xpath('//Topic/Title').text
+ end
+
def markup_doc
@markup_doc ||= Nokogiri::XML markup, nil, 'UTF-8'
end
diff --git a/modules/bcf/app/views/bcf/issues/_render_issues.html.erb b/modules/bcf/app/views/bcf/issues/_render_issues.html.erb
index 4152966f476..02eae392eb8 100644
--- a/modules/bcf/app/views/bcf/issues/_render_issues.html.erb
+++ b/modules/bcf/app/views/bcf/issues/_render_issues.html.erb
@@ -6,11 +6,10 @@
<% highlighting_class << ' -failed' if issue.errors.present? %>
- <%= issue.title %>
-
<% if issue.work_package %>
<%= link_to_work_package(issue.work_package) %>
-
+ <% else %>
+ <%= issue.imported_title %>
<% end %>
<% if issue.errors.present? %>
diff --git a/modules/bcf/db/migrate/20191121140202_migrate_xml_viewpoint_to_json.rb b/modules/bcf/db/migrate/20191121140202_migrate_xml_viewpoint_to_json.rb
index 296547b5713..8f8fc3e00a7 100644
--- a/modules/bcf/db/migrate/20191121140202_migrate_xml_viewpoint_to_json.rb
+++ b/modules/bcf/db/migrate/20191121140202_migrate_xml_viewpoint_to_json.rb
@@ -8,7 +8,7 @@ class MigrateXmlViewpointToJson < ActiveRecord::Migration[6.0]
# Convert viewpoints
::Bcf::Viewpoint.reset_column_information
::Bcf::Viewpoint.find_each do |resource|
- mapper = ::OpenProject::Bcf::BcfJson::ViewpointMapper
+ mapper = ::OpenProject::Bcf::BcfJson::ViewpointReader
.new(resource.uuid, resource.viewpoint)
resource.update_column(:json_viewpoint, mapper.result)
diff --git a/modules/bcf/lib/open_project/bcf/bcf_json/viewpoint_mapper.rb b/modules/bcf/lib/open_project/bcf/bcf_json/viewpoint_reader.rb
similarity index 99%
rename from modules/bcf/lib/open_project/bcf/bcf_json/viewpoint_mapper.rb
rename to modules/bcf/lib/open_project/bcf/bcf_json/viewpoint_reader.rb
index 0c809c7bd29..4603b61277b 100644
--- a/modules/bcf/lib/open_project/bcf/bcf_json/viewpoint_mapper.rb
+++ b/modules/bcf/lib/open_project/bcf/bcf_json/viewpoint_reader.rb
@@ -1,6 +1,6 @@
module OpenProject::Bcf
module BcfJson
- class ViewpointMapper
+ class ViewpointReader
ROOT_NODE ||= 'VisualizationInfo'.freeze
attr_reader :uuid, :xml
diff --git a/modules/bcf/lib/open_project/bcf/bcf_xml/base_writer.rb b/modules/bcf/lib/open_project/bcf/bcf_xml/base_writer.rb
new file mode 100644
index 00000000000..ad86e1885ab
--- /dev/null
+++ b/modules/bcf/lib/open_project/bcf/bcf_xml/base_writer.rb
@@ -0,0 +1,67 @@
+##
+# Creates or updates a BCF issue and markup from a work package
+module OpenProject::Bcf::BcfXml
+ class BaseWriter
+ attr_reader :markup_doc
+
+ def initialize
+ @markup_doc = build_markup_document
+ end
+
+ protected
+
+ def root_node
+ raise NotImplementedError
+ end
+
+ def root_node_attributes
+ {}
+ end
+
+ ##
+ # Initial markup file as basic BCF compliant xml
+ def build_markup_document
+ Nokogiri::XML::Builder
+ .new(:encoding => 'UTF-8') do |xml|
+ xml.comment created_by_comment
+ xml.send(root_node,
+ "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
+ "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
+ **root_node_attributes)
+ end
+ .doc
+ end
+
+ def prepend_into_or_insert(parent_node, node)
+ if first_child = parent_node.children.select(&:element?)&.first
+ first_child.previous = node
+ else
+ node.parent = parent_node
+ end
+ end
+
+ def fetch(parent_node, name)
+ node = parent_node.at(name) || Nokogiri::XML::Node.new(name, markup_doc)
+ node.parent = parent_node unless node.parent.present?
+ node
+ end
+
+ ##
+ #
+ def created_by_comment
+ " Created by #{Setting.app_title} #{OpenProject::VERSION} at #{Time.now} "
+ end
+
+ def to_bcf_datetime(date_time)
+ date_time.utc.iso8601
+ end
+
+ def to_bcf_date(date)
+ date.iso8601
+ end
+
+ def url_helpers
+ @url_helpers ||= OpenProject::StaticRouting::StaticUrlHelpers.new
+ end
+ end
+end
diff --git a/modules/bcf/lib/open_project/bcf/bcf_xml/exporter.rb b/modules/bcf/lib/open_project/bcf/bcf_xml/exporter.rb
index 87ab075b914..630ce25ae4d 100644
--- a/modules/bcf/lib/open_project/bcf/bcf_xml/exporter.rb
+++ b/modules/bcf/lib/open_project/bcf/bcf_xml/exporter.rb
@@ -131,7 +131,7 @@ module OpenProject::Bcf::BcfXml
snapshot_file = File.join(issue_dir, vp.snapshot.filename)
# Copy the files
- dump_file vp_file, vp.viewpoint
+ dump_file vp_file, ViewpointWriter.new(vp).to_xml
FileUtils.cp vp.snapshot.local_path, snapshot_file
files << vp_file << snapshot_file
diff --git a/modules/bcf/lib/open_project/bcf/bcf_xml/issue_reader.rb b/modules/bcf/lib/open_project/bcf/bcf_xml/issue_reader.rb
index 3090d3112b8..e77d97bfb16 100644
--- a/modules/bcf/lib/open_project/bcf/bcf_xml/issue_reader.rb
+++ b/modules/bcf/lib/open_project/bcf/bcf_xml/issue_reader.rb
@@ -193,7 +193,7 @@ module OpenProject::Bcf::BcfXml
uuid: vp[:uuid],
# Save the viewpoint as json
- viewpoint: viewpoint_as_json(vp[:uuid], vp[:viewpoint]),
+ json_viewpoint: viewpoint_as_json(vp[:uuid], read_entry(vp[:viewpoint])),
viewpoint_name: vp[:viewpoint],
# Save the snapshot as file attachment
@@ -238,9 +238,9 @@ module OpenProject::Bcf::BcfXml
##
# Map the xml viewpoint as json
def viewpoint_as_json(uuid, xml)
- ::OpenProject::Bcf::BcfJson::ViewpointMapper
+ ::OpenProject::Bcf::BcfJson::ViewpointReader
.new(uuid, xml)
- .to_json
+ .result
end
def new_comment(comment_data)
diff --git a/modules/bcf/lib/open_project/bcf/bcf_xml/issue_writer.rb b/modules/bcf/lib/open_project/bcf/bcf_xml/issue_writer.rb
index 1304f642677..0da31e0bf5c 100644
--- a/modules/bcf/lib/open_project/bcf/bcf_xml/issue_writer.rb
+++ b/modules/bcf/lib/open_project/bcf/bcf_xml/issue_writer.rb
@@ -1,7 +1,7 @@
##
# Creates or updates a BCF issue and markup from a work package
module OpenProject::Bcf::BcfXml
- class IssueWriter
+ class IssueWriter < BaseWriter
attr_reader :work_package, :issue, :markup_doc, :markup_node
TOPIC_SEQUENCE = [
@@ -34,11 +34,11 @@ module OpenProject::Bcf::BcfXml
@work_package = work_package
@issue = find_or_initialize_issue
- # Read the existing markup XML or build an empty one
- @markup_doc = build_markup_document
+ # Create markup document
+ super()
# Remember root markup node for easier access
- @markup_node = @markup_doc.at_xpath('/Markup')
+ @markup_node = markup_doc.at_xpath('/Markup')
end
def update
@@ -58,7 +58,11 @@ module OpenProject::Bcf::BcfXml
issue.save!
end
- private
+ protected
+
+ def root_node
+ :Markup
+ end
##
# Get the nokogiri document from the markup xml
@@ -66,16 +70,7 @@ module OpenProject::Bcf::BcfXml
if issue.markup
Nokogiri::XML issue.markup, &:noblanks
else
- build_initial_markup_xml.doc
- end
- end
-
- ##
- # Initial markup file as basic BCF compliant xml
- def build_initial_markup_xml
- Nokogiri::XML::Builder.new do |xml|
- xml.comment created_by_comment
- xml.Markup "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance", "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema"
+ super
end
end
@@ -124,12 +119,6 @@ module OpenProject::Bcf::BcfXml
end
end
- def fetch(parent_node, name)
- node = parent_node.at(name) || Nokogiri::XML::Node.new(name, markup_doc)
- node.parent = parent_node unless node.parent.present?
- node
- end
-
def topic_attributes(topic_node)
topic_node['Guid'] = issue.uuid
topic_node['TopicType'] = work_package.type.name # TODO: Looks wrong to me. Probably better to use original TopicType?
@@ -239,28 +228,10 @@ module OpenProject::Bcf::BcfXml
end
end
- ##
- #
- def created_by_comment
- " Created by #{Setting.app_title} #{OpenProject::VERSION} at #{Time.now} "
- end
-
##
# Find existing issue or create new
def find_or_initialize_issue
::Bcf::Issue.find_or_initialize_by(work_package: work_package)
end
-
- def to_bcf_datetime(date_time)
- date_time.utc.iso8601
- end
-
- def to_bcf_date(date)
- date.iso8601
- end
-
- def url_helpers
- @url_helpers ||= OpenProject::StaticRouting::StaticUrlHelpers.new
- end
end
end
diff --git a/modules/bcf/lib/open_project/bcf/bcf_xml/viewpoint_writer.rb b/modules/bcf/lib/open_project/bcf/bcf_xml/viewpoint_writer.rb
new file mode 100644
index 00000000000..fd089b6952f
--- /dev/null
+++ b/modules/bcf/lib/open_project/bcf/bcf_xml/viewpoint_writer.rb
@@ -0,0 +1,182 @@
+##
+# Creates or updates a BCF issue and markup from a work package
+module OpenProject::Bcf::BcfXml
+ class ViewpointWriter < BaseWriter
+ attr_reader :viewpoint
+
+ def initialize(viewpoint)
+ @viewpoint = viewpoint
+ super()
+ end
+
+ def to_xml
+ doc.to_xml(indent: 2)
+ end
+
+ def doc
+ @doc ||= begin
+ viewpoint_node = fetch(markup_doc, root_node.to_s)
+
+ Nokogiri::XML::Builder.with(viewpoint_node) do |xml|
+ components xml
+
+ camera('orthogonal_camera', xml)
+ camera('perspective_camera', xml)
+
+ lines xml
+ clipping_planes xml
+ bitmaps xml
+ end
+
+ markup_doc
+ end
+ end
+
+ protected
+
+ def root_node
+ :VisualizationInfo
+ end
+
+ def root_node_attributes
+ { Guid: viewpoint.uuid }
+ end
+
+ def dig_json(*args)
+ viewpoint.json_viewpoint.dig(*args)
+ end
+
+ def components(xml)
+ return unless dig_json('components')
+
+ xml.Components do
+ view_setup_hints xml
+ selected_components xml
+ visibility xml
+ coloring xml
+ end
+ end
+
+ def view_setup_hints(xml)
+ return unless (setup_hash = dig_json('components', 'visibility', 'view_setup_hints'))
+
+ xml.ViewSetupHints(camelized(setup_hash))
+ end
+
+ def selected_components(xml)
+ return unless (selected = dig_json('components', 'selection'))
+
+ xml.Selection do
+ selected.each do |comp_hash|
+ xml.Component camelized(comp_hash)
+ end
+ end
+ end
+
+ def visibility(xml)
+ return unless (visibility_hash = dig_json 'components', 'visibility')
+
+ xml.Visibility(DefaultVisibility: visibility_hash['default_visibility']) do
+ exceptions = visibility_hash['exceptions']
+ next unless exceptions
+
+ xml.Exceptions do
+ Array.wrap(exceptions).each do |comp_hash|
+ xml.Component camelized(comp_hash)
+ end
+ end
+ end
+ end
+
+ def coloring(xml)
+ return unless (colors = dig_json 'components', 'coloring')
+
+ xml.Coloring do
+ Array.wrap(colors).each do |color|
+ xml.Color Color: color['color'].delete_prefix('#') do
+ Array.wrap(color['components']).each do |comp_hash|
+ xml.Component camelized(comp_hash)
+ end
+ end
+ end
+ end
+ end
+
+ def camera(type, xml)
+ return unless (camera = dig_json(type))
+
+ xml.send(type.camelize) do
+ %w[CameraViewPoint CameraDirection CameraUpVector].each do |entry|
+ xml.send(entry) do
+ coords = camera[entry.underscore]
+ to_xml_coords(coords, xml)
+ end
+ end
+ xml.FieldOfView convert_float(camera['field_of_view'])
+ end
+ end
+
+ def lines(xml)
+ return unless (lines = dig_json 'lines')
+
+ xml.Lines do
+ Array.wrap(lines).each do |line|
+ xml.Line do
+ xml.StartPoint { to_xml_coords(line['start_point'], xml) }
+ xml.EndPoint { to_xml_coords(line['end_point'], xml) }
+ end
+ end
+ end
+ end
+
+ def clipping_planes(xml)
+ return unless (planes = dig_json 'clipping_planes')
+
+ xml.ClippingPlanes do
+ Array.wrap(planes).each do |plane|
+ xml.ClippingPlane do
+ xml.Location { to_xml_coords(plane['location'], xml) }
+ xml.Direction { to_xml_coords(plane['direction'], xml) }
+ end
+ end
+ end
+ end
+
+ def bitmaps(xml)
+ return unless (entries = dig_json 'bitmaps')
+
+ # Bitmaps are rendered flat, whyever that is
+ entries.each do |bitmap|
+ xml.Bitmaps do
+ xml.Bitmap bitmap['bitmap_type'].upcase
+ xml.Reference bitmap['bitmap_data']
+ xml.Location { to_xml_coords bitmap['location'], xml }
+ xml.Normal { to_xml_coords bitmap['normal'], xml }
+ xml.Up { to_xml_coords bitmap['up'], xml }
+ xml.Height convert_float(bitmap['height'])
+ end
+ end
+ end
+
+ ##
+ # Helper to transform a hash into camelized keys
+ def camelized(hash)
+ hash.transform_keys(&:camelize)
+ end
+
+ ##
+ # Convert a float to BCF format that strips
+ # insignificant zeros
+ def convert_float(val)
+ val.to_s.gsub(/(\.)0+$/, '')
+ end
+
+ ##
+ # Helper to render X,Y,Z hash as set of nodes
+ def to_xml_coords(hash, xml)
+ hash.each do |key, val|
+ xml.send(key.to_s.upcase, convert_float(val))
+ end
+ end
+ end
+end
diff --git a/modules/bcf/spec/factories/bcf_viewpoint_factory.rb b/modules/bcf/spec/factories/bcf_viewpoint_factory.rb
index 5c08dbf9900..e9952a93c65 100644
--- a/modules/bcf/spec/factories/bcf_viewpoint_factory.rb
+++ b/modules/bcf/spec/factories/bcf_viewpoint_factory.rb
@@ -38,10 +38,10 @@ FactoryBot.define do
new_uuid = SecureRandom.uuid
uuid { new_uuid }
viewpoint_name { "full_viewpoint.bcfv" }
- viewpoint do
- file = OpenProject::Bcf::Engine.root.join("spec/fixtures/viewpoints/#{viewpoint_name}.xml")
+ json_viewpoint do
+ file = OpenProject::Bcf::Engine.root.join("spec/fixtures/viewpoints/#{viewpoint_name}.json")
if file.readable?
- file.read
+ JSON.parse(file.read)
else
warn "Viewpoint name #{viewpoint_name} doesnt map to a viewpoint fixture"
end
diff --git a/modules/bcf/spec/fixtures/viewpoints/full_viewpoint.bcfv.xml b/modules/bcf/spec/fixtures/viewpoints/full_viewpoint.bcfv.xml
index af60fd30ae3..db0b1da07b0 100644
--- a/modules/bcf/spec/fixtures/viewpoints/full_viewpoint.bcfv.xml
+++ b/modules/bcf/spec/fixtures/viewpoints/full_viewpoint.bcfv.xml
@@ -1,6 +1,6 @@
-
+
@@ -120,13 +120,13 @@
-0.9999999999999999
- 1.253656364893038E-16
- 0.0
+ 1.253656364893038e-16
+ 0
- -5.43903050550883E-34
- -4.338533794284917E-18
- 1.0
+ -5.43903050550883e-34
+ -4.338533794284917e-18
+ 1
1666.1814563907683
@@ -140,13 +140,13 @@
-3
- 1.253656364893038E-16
- 0.0
+ 1.253656364893038e-16
+ 0
30.1234
-9000
- 1.0
+ 1
10
diff --git a/modules/bcf/spec/fixtures/viewpoints/minimal.bcfv.xml b/modules/bcf/spec/fixtures/viewpoints/minimal.bcfv.xml
index 780ee5877a2..d67ae08e076 100644
--- a/modules/bcf/spec/fixtures/viewpoints/minimal.bcfv.xml
+++ b/modules/bcf/spec/fixtures/viewpoints/minimal.bcfv.xml
@@ -1,5 +1,5 @@
-
+
12.3456789
@@ -7,8 +7,8 @@
-1234.1234
- -1.0
- -2.0
+ -1
+ -2
-3
diff --git a/modules/bcf/spec/fixtures/viewpoints/neubau_sc_1.bcfv.xml b/modules/bcf/spec/fixtures/viewpoints/neubau_sc_1.bcfv.xml
index b9f9669bbb2..8f63e2b96a0 100644
--- a/modules/bcf/spec/fixtures/viewpoints/neubau_sc_1.bcfv.xml
+++ b/modules/bcf/spec/fixtures/viewpoints/neubau_sc_1.bcfv.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/modules/bcf/spec/fixtures/viewpoints/neubau_sc_1_fixed.bcfv.xml b/modules/bcf/spec/fixtures/viewpoints/neubau_sc_1_fixed.bcfv.xml
new file mode 100644
index 00000000000..d596abe0dd0
--- /dev/null
+++ b/modules/bcf/spec/fixtures/viewpoints/neubau_sc_1_fixed.bcfv.xml
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 58.1038
+ 16.5493
+ 24.7329
+
+
+ 1.28974
+ 1.2105
+ -0.569962
+
+
+ 0.223629
+ 0.209889
+ 0.951807
+
+ 60
+
+
\ No newline at end of file
diff --git a/modules/bcf/spec/lib/open_project/bcf/bcf_json/viewpoint_mapper_shared_examples.rb b/modules/bcf/spec/lib/open_project/bcf/bcf_json/viewpoint_reader_shared_examples.rb
similarity index 100%
rename from modules/bcf/spec/lib/open_project/bcf/bcf_json/viewpoint_mapper_shared_examples.rb
rename to modules/bcf/spec/lib/open_project/bcf/bcf_json/viewpoint_reader_shared_examples.rb
diff --git a/modules/bcf/spec/lib/open_project/bcf/bcf_json/viewpoint_mapper_spec.rb b/modules/bcf/spec/lib/open_project/bcf/bcf_json/viewpoint_reader_spec.rb
similarity index 95%
rename from modules/bcf/spec/lib/open_project/bcf/bcf_json/viewpoint_mapper_spec.rb
rename to modules/bcf/spec/lib/open_project/bcf/bcf_json/viewpoint_reader_spec.rb
index 60fa0e2725f..a07d03a03a8 100644
--- a/modules/bcf/spec/lib/open_project/bcf/bcf_json/viewpoint_mapper_spec.rb
+++ b/modules/bcf/spec/lib/open_project/bcf/bcf_json/viewpoint_reader_spec.rb
@@ -27,9 +27,9 @@
#++
require 'spec_helper'
-require_relative './viewpoint_mapper_shared_examples.rb'
+require_relative './viewpoint_reader_shared_examples.rb'
-describe OpenProject::Bcf::BcfJson::ViewpointMapper do
+describe OpenProject::Bcf::BcfJson::ViewpointReader do
let(:instance) { described_class.new xml_viewpoint.uuid, xml_viewpoint.viewpoint }
subject { instance.result }
diff --git a/modules/bcf/spec/lib/open_project/bcf/bcf_xml/viewpoint_writer_spec.rb b/modules/bcf/spec/lib/open_project/bcf/bcf_xml/viewpoint_writer_spec.rb
new file mode 100644
index 00000000000..17fca97b2e1
--- /dev/null
+++ b/modules/bcf/spec/lib/open_project/bcf/bcf_xml/viewpoint_writer_spec.rb
@@ -0,0 +1,96 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2019 the OpenProject Foundation (OPF)
+#
+# 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-2017 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 docs/COPYRIGHT.rdoc for more details.
+#++
+
+require 'spec_helper'
+require 'compare-xml'
+
+describe OpenProject::Bcf::BcfXml::ViewpointWriter do
+ let(:writer_instance) { described_class.new json_resource }
+ let(:reader_instance) { ::OpenProject::Bcf::BcfJson::ViewpointReader.new xml_resource.uuid, subject.to_xml }
+ let(:xml_comparison) { Nokogiri::XML(xml_resource.viewpoint) }
+ let(:json_comparison) { json_resource.raw_json_viewpoint }
+
+ subject { writer_instance.doc }
+
+ shared_examples 'converts back to xml' do
+ it 'the output of writer is XML-equal to the provided XML viewpoint' do
+ results = CompareXML.equivalent?(
+ subject,
+ xml_comparison,
+ collapse_whitespace: false,
+ verbose: true
+ )
+
+ if results.length > 0
+ puts subject.to_xml
+ raise "Expected documents to be equal. Found diffs:\n#{results.join("\n")}"
+ end
+ end
+
+ it 'contains the root node' do
+ expect(writer_instance.doc.at('VisualizationInfo')).to be_present
+ end
+
+ it 'goes full circle comparing back to JSON' do
+ expect(reader_instance.to_json).to be_json_eql json_comparison
+ end
+ end
+
+ describe 'with minimal example' do
+ let_it_be(:json_resource) do
+ FactoryBot.build_stubbed :bcf_viewpoint, uuid: '{{UUID}}', viewpoint_name: 'minimal.bcfv'
+ end
+ let_it_be(:xml_resource) do
+ FactoryBot.build_stubbed :xml_viewpoint, uuid: '{{UUID}}', viewpoint_name: 'minimal.bcfv'
+ end
+
+ it_behaves_like 'converts back to xml'
+ end
+
+ describe 'with full viewpoint' do
+ let_it_be(:json_resource) do
+ FactoryBot.build_stubbed :bcf_viewpoint, uuid: '{{UUID}}', viewpoint_name: 'full_viewpoint.bcfv'
+ end
+ let_it_be(:xml_resource) do
+ FactoryBot.build_stubbed :xml_viewpoint, uuid: '{{UUID}}', viewpoint_name: 'full_viewpoint.bcfv'
+ end
+
+ it_behaves_like 'converts back to xml'
+ end
+
+ describe 'with real-world neuhaus_sc_1 example' do
+ let_it_be(:json_resource) do
+ FactoryBot.build_stubbed :bcf_viewpoint, uuid: '{{UUID}}', viewpoint_name: 'neubau_sc_1.bcfv'
+ end
+ let_it_be(:xml_resource) do
+ FactoryBot.build_stubbed :xml_viewpoint, uuid: '{{UUID}}', viewpoint_name: 'neubau_sc_1_fixed.bcfv'
+ end
+
+ it_behaves_like 'converts back to xml'
+ end
+end
diff --git a/modules/bcf/spec/requests/api/bcf/v2_1/viewpoints_api_spec.rb b/modules/bcf/spec/requests/api/bcf/v2_1/viewpoints_api_spec.rb
index 1c6ee45f06f..40c45a50ac8 100644
--- a/modules/bcf/spec/requests/api/bcf/v2_1/viewpoints_api_spec.rb
+++ b/modules/bcf/spec/requests/api/bcf/v2_1/viewpoints_api_spec.rb
@@ -54,7 +54,7 @@ describe 'BCF 2.1 viewpoints resource', type: :request, content_type: :json, wit
shared_let(:bcf_issue) { FactoryBot.create(:bcf_issue_with_viewpoint, work_package: work_package) }
let(:viewpoint) { bcf_issue.viewpoints.first }
- let(:viewpoint_json) { ::OpenProject::Bcf::BcfJson::ViewpointMapper.new(viewpoint).result }
+ let(:viewpoint_json) { ::OpenProject::Bcf::BcfJson::ViewpointReader.new(viewpoint).result }
subject(:response) { last_response }
describe 'GET /api/bcf/2.1/projects/:project_id/topics/:topic/viewpoints' do