From 8f963589ed14726d4ab3afb27479ea87b3c3d755 Mon Sep 17 00:00:00 2001 From: Magnus Jacobsson Date: Sat, 13 Aug 2022 15:53:15 +0200 Subject: [PATCH] tests: SVGAnalyzer: add support for retrieving the Graphviz node fillcolor --- tests/CMakeLists.txt | 1 + tests/graphviz_node.cpp | 8 +++++ tests/graphviz_node.h | 3 ++ tests/test_node_fillcolor.cpp | 58 +++++++++++++++++++++++++++++++++++ tests/test_utilities.cpp | 20 ++++++++++++ tests/test_utilities.h | 2 ++ 6 files changed, 92 insertions(+) create mode 100644 tests/test_node_fillcolor.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9d1b82692..55f63ae05 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -87,6 +87,7 @@ CREATE_TEST(GVLayout_construction) CREATE_TEST(GVLayout_render) CREATE_TEST(neatopack) CREATE_TEST(node_color) +CREATE_TEST(node_fillcolor) CREATE_TEST(node_penwidth) CREATE_TEST(rankdir) CREATE_TEST(simple) diff --git a/tests/graphviz_node.cpp b/tests/graphviz_node.cpp index 37b356f08..630c34264 100644 --- a/tests/graphviz_node.cpp +++ b/tests/graphviz_node.cpp @@ -17,6 +17,14 @@ std::string GraphvizNode::color() const { return SVG::to_dot_color(stroke, stroke_opacity); } +std::string GraphvizNode::fillcolor() const { + const auto fill = m_svg_g_element.attribute_from_subtree( + &SVG::SVGAttributes::fill, &SVG::SVGElement::is_shape_element, ""); + const auto fill_opacity = m_svg_g_element.attribute_from_subtree( + &SVG::SVGAttributes::fill_opacity, &SVG::SVGElement::is_shape_element, 1); + return SVG::to_dot_color(fill, fill_opacity); +} + double GraphvizNode::penwidth() const { return m_svg_g_element.attribute_from_subtree( &SVG::SVGAttributes::stroke_width, &SVG::SVGElement::is_shape_element, 1); diff --git a/tests/graphviz_node.h b/tests/graphviz_node.h index b2af29e1f..dbe8b46ff 100644 --- a/tests/graphviz_node.h +++ b/tests/graphviz_node.h @@ -22,6 +22,9 @@ public: /// Return the node's `color` attribute in RGB hex format if the opacity is /// 100 % or in RGBA hex format otherwise. std::string color() const; + /// Return the node's `fillcolor` attribute in RGB hex format if the opacity + /// is 100 % or in RGBA hex format otherwise. + std::string fillcolor() const; /// Return the node's `node_id` as defined by the DOT language std::string_view node_id() const; /// Return the node's `penwidth` attribute diff --git a/tests/test_node_fillcolor.cpp b/tests/test_node_fillcolor.cpp new file mode 100644 index 000000000..d3e1bb519 --- /dev/null +++ b/tests/test_node_fillcolor.cpp @@ -0,0 +1,58 @@ +#include + +#include +#include + +#include "svg_analyzer.h" +#include "test_utilities.h" + +TEST_CASE("Node fillcolor", + "Test that the Graphviz `fillcolor` attribute is used to set " + "the `fill` and `fill-opacity` attributes correctly for nodes in the " + "generated SVG") { + + const auto shape = GENERATE(filter( + [](std::string_view shape) { + return !node_shapes_without_svg_shape.contains(shape); + }, + from_range(all_node_shapes))); + INFO(fmt::format("Shape: {}", shape)); + + const auto node_rgb_fillcolor = + GENERATE("#000000", "#ffffff", "#ff0000", "#aa0f55"); + const auto opacity = GENERATE(0, 200, 255); + const auto node_rgba_fillcolor = + fmt::format("{}{:02x}", node_rgb_fillcolor, opacity); + INFO(fmt::format("Node fillcolor: {}", node_rgba_fillcolor)); + + auto dot = fmt::format( + "digraph g1 {{node [shape={} style=filled fillcolor=\"{}\"]; a -> b}}", + shape, node_rgba_fillcolor); + + const auto engine = "dot"; + auto svg_analyzer = SVGAnalyzer::make_from_dot(dot, engine); + + const auto expected_node_fillcolor = [&]() -> const std::string { + if (opacity == 255) { + return node_rgb_fillcolor; + } + if (opacity == 0) { + return "#00000000"; // transparent/none + } + return node_rgba_fillcolor; + }(); + + for (const auto &graph : svg_analyzer.graphs()) { + for (const auto &node : graph.nodes()) { + if (contains_multiple_shapes_with_different_fill(shape) && opacity != 0) { + // FIXME: these node shapes consists of both a transparent shape and a + // shape with the specified filllcolor which the node fillcolor + // retrieval function cannot yet handle (unless the specified fillcolor + // is also transparent) and will throw an exception for + REQUIRE_THROWS_AS(node.fillcolor(), std::runtime_error); + continue; + } + CHECK(node.fillcolor() == expected_node_fillcolor); + } + } +} diff --git a/tests/test_utilities.cpp b/tests/test_utilities.cpp index b7e964370..161531420 100644 --- a/tests/test_utilities.cpp +++ b/tests/test_utilities.cpp @@ -144,16 +144,36 @@ const std::unordered_set all_node_shapes = { "lpromoter" // }; +static const std::unordered_set + node_shapes_containing_multiple_same_shapes_with_different_fill = { + "cylinder", // two paths + "doublecircle", // two ellipses + "doubleoctagon", // two polygons + "tripleoctagon", // three polygons +}; + bool contains_polygon_shape(const std::string_view shape) { return node_shapes_consisting_of_polygon.contains(shape) || node_shapes_consisting_of_polygon_and_polyline.contains(shape); } +bool contains_polyline_shape(const std::string_view shape) { + return node_shapes_consisting_of_ellipse_and_polyline.contains(shape) || + node_shapes_consisting_of_polygon_and_polyline.contains(shape); +} + bool contains_ellipse_shape(const std::string_view shape) { return node_shapes_consisting_of_ellipse.contains(shape) || node_shapes_consisting_of_ellipse_and_polyline.contains(shape); } +bool contains_multiple_shapes_with_different_fill( + const std::string_view shape) { + return node_shapes_containing_multiple_same_shapes_with_different_fill + .contains(shape) || + contains_polyline_shape(shape); +} + const std::unordered_set primitive_polygon_arrow_shapes = { "crow", "diamond", "inv", "normal", "vee"}; diff --git a/tests/test_utilities.h b/tests/test_utilities.h index 9bab6cac2..d3f4c7d43 100644 --- a/tests/test_utilities.h +++ b/tests/test_utilities.h @@ -19,7 +19,9 @@ extern const std::unordered_set extern const std::unordered_set node_shapes_without_svg_shape; bool contains_ellipse_shape(std::string_view shape); +bool contains_multiple_shapes_with_different_fill(std::string_view shape); bool contains_polygon_shape(std::string_view shape); +bool contains_polyline_shape(std::string_view shape); /// arrow shapes extern const std::unordered_set -- 2.40.0