From e3fc4c0e4a2bef0429aab433fc8a0e05490b1a90 Mon Sep 17 00:00:00 2001 From: Thibaud Date: Fri, 27 Mar 2026 16:27:23 +0100 Subject: [PATCH] test: add SVGDraftman direct visitor tests Add 27 tests covering each visit*() method in isolation: - visitRectangle: dimensions, filled/stroked/both/neither color combos - visitCircle: center calculation, fill/stroke variants - visitTriangle: polygon points, fill style, stroke style - visitText: content, position offset, font attributes, color fallbacks - visitCollection: empty, multiple children, nested collections - generateSVG: XML declaration, SVG namespace, closing tag, shape content Closes #9 --- .../ui/visitors/SVGDraftmanTest.java | 370 ++++++++++++++++++ 1 file changed, 370 insertions(+) create mode 100644 src/test/java/ovh/gasser/newshapes/ui/visitors/SVGDraftmanTest.java diff --git a/src/test/java/ovh/gasser/newshapes/ui/visitors/SVGDraftmanTest.java b/src/test/java/ovh/gasser/newshapes/ui/visitors/SVGDraftmanTest.java new file mode 100644 index 0000000..287fc79 --- /dev/null +++ b/src/test/java/ovh/gasser/newshapes/ui/visitors/SVGDraftmanTest.java @@ -0,0 +1,370 @@ +package ovh.gasser.newshapes.ui.visitors; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import ovh.gasser.newshapes.attributes.ColorAttributes; +import ovh.gasser.newshapes.shapes.*; + +import java.awt.*; +import java.io.PrintWriter; +import java.io.StringWriter; + +import static org.junit.jupiter.api.Assertions.*; + +class SVGDraftmanTest { + + private StringWriter buffer; + private PrintWriter writer; + private SVGDraftman draftman; + + @BeforeEach + void setUp() { + buffer = new StringWriter(); + writer = new PrintWriter(buffer); + draftman = new SVGDraftman(writer); + } + + private String output() { + writer.flush(); + return buffer.toString(); + } + + // ── visitRectangle ────────────────────────────────────────────── + + @Nested + class VisitRectangleTests { + + @Test + void testRectangleElementWithCorrectDimensions() { + SRectangle rect = SRectangle.create(10, 20, 100, 50); + draftman.visitRectangle(rect); + + String svg = output(); + assertTrue(svg.contains(" element"); + assertTrue(svg.contains("width=\"100\""), "Should have correct width"); + assertTrue(svg.contains("height=\"50\""), "Should have correct height"); + assertTrue(svg.contains("x=\"10\""), "Should have correct x"); + assertTrue(svg.contains("y=\"20\""), "Should have correct y"); + } + + @Test + void testRectangleFilledOnly() { + SRectangle rect = SRectangle.create(0, 0, 30, 30); + rect.addAttributes(new ColorAttributes(true, false, Color.RED, Color.BLACK)); + draftman.visitRectangle(rect); + + String svg = output(); + assertTrue(svg.contains("fill=\"#ff0000\""), "Should contain fill color"); + assertFalse(svg.contains("stroke="), "Should not contain stroke when stroked=false"); + } + + @Test + void testRectangleStrokedOnly() { + SRectangle rect = SRectangle.create(0, 0, 30, 30); + rect.addAttributes(new ColorAttributes(false, true, Color.RED, Color.BLUE)); + draftman.visitRectangle(rect); + + String svg = output(); + assertTrue(svg.contains("stroke=\"#0000ff\""), "Should contain stroke color"); + assertTrue(svg.contains("stroke-width=\"1\""), "Should contain stroke-width"); + assertTrue(svg.contains("fill=\"none\""), "Should have fill=none when not filled"); + } + + @Test + void testRectangleFilledAndStroked() { + SRectangle rect = SRectangle.create(0, 0, 30, 30); + rect.addAttributes(new ColorAttributes(true, true, Color.RED, Color.BLUE)); + draftman.visitRectangle(rect); + + String svg = output(); + assertTrue(svg.contains("fill=\"#ff0000\""), "Should contain fill color"); + assertTrue(svg.contains("stroke=\"#0000ff\""), "Should contain stroke color"); + } + + @Test + void testRectangleNeitherFilledNorStroked() { + SRectangle rect = SRectangle.create(0, 0, 30, 30); + rect.addAttributes(new ColorAttributes(false, false, Color.RED, Color.BLUE)); + draftman.visitRectangle(rect); + + String svg = output(); + assertTrue(svg.contains("fill=\"none\""), "Should have fill=none"); + assertFalse(svg.contains("stroke="), "Should not have stroke attribute"); + } + } + + // ── visitCircle ───────────────────────────────────────────────── + + @Nested + class VisitCircleTests { + + @Test + void testCircleElementWithCorrectAttributes() { + SCircle circle = SCircle.create(50, 60, 30); + draftman.visitCircle(circle); + + String svg = output(); + assertTrue(svg.contains(" element"); + // cx = x + r = 50 + 30 = 80, cy = y + r = 60 + 30 = 90 + assertTrue(svg.contains("cx=\"80\""), "cx should be x + radius"); + assertTrue(svg.contains("cy=\"90\""), "cy should be y + radius"); + assertTrue(svg.contains("r=\"30\""), "Should have correct radius"); + } + + @Test + void testCircleFilledOnly() { + SCircle circle = SCircle.create(0, 0, 20); + circle.addAttributes(new ColorAttributes(true, false, Color.GREEN, Color.BLACK)); + draftman.visitCircle(circle); + + String svg = output(); + assertTrue(svg.contains("fill=\"#00ff00\""), "Should contain fill color"); + assertFalse(svg.contains("stroke="), "Should not contain stroke"); + } + + @Test + void testCircleStrokedOnly() { + SCircle circle = SCircle.create(0, 0, 20); + circle.addAttributes(new ColorAttributes(false, true, Color.RED, Color.MAGENTA)); + draftman.visitCircle(circle); + + String svg = output(); + assertTrue(svg.contains("stroke=\"#ff00ff\""), "Should contain stroke color"); + assertTrue(svg.contains("fill=\"none\""), "Should have fill=none"); + } + + @Test + void testCircleFilledAndStroked() { + SCircle circle = SCircle.create(0, 0, 15); + circle.addAttributes(new ColorAttributes(true, true, Color.YELLOW, Color.BLACK)); + draftman.visitCircle(circle); + + String svg = output(); + assertTrue(svg.contains("fill=\"#ffff00\""), "Should contain fill color"); + assertTrue(svg.contains("stroke=\"#000000\""), "Should contain stroke color"); + } + } + + // ── visitTriangle ─────────────────────────────────────────────── + + @Nested + class VisitTriangleTests { + + @Test + void testTriangleProducesPolygonElement() { + STriangle tri = STriangle.create(10, 20, 40, Color.RED, Color.BLACK); + draftman.visitTriangle(tri); + + String svg = output(); + assertTrue(svg.contains(" element"); + assertTrue(svg.contains("points="), "Should have points attribute"); + } + + @Test + void testTrianglePointsAreCorrect() { + STriangle tri = STriangle.create(10, 20, 40, Color.RED, Color.BLACK); + draftman.visitTriangle(tri); + + String svg = output(); + // bottom-left: (x, y+size) = (10, 60) + assertTrue(svg.contains("10,60"), "Should contain bottom-left point"); + // top-center: (x + size/2, y) = (30, 20) + assertTrue(svg.contains("30,20"), "Should contain top-center point"); + // bottom-right: (x + size, y + size) = (50, 60) + assertTrue(svg.contains("50,60"), "Should contain bottom-right point"); + } + + @Test + void testTriangleFilledOnly() { + STriangle tri = STriangle.create(0, 0, 20, Color.BLUE, Color.BLACK); + tri.addAttributes(new ColorAttributes(true, false, Color.BLUE, Color.BLACK)); + draftman.visitTriangle(tri); + + String svg = output(); + assertTrue(svg.contains("fill:#0000ff"), "Should contain fill style"); + assertFalse(svg.contains("stroke:"), "Should not contain stroke style when not stroked"); + } + + @Test + void testTriangleStrokedAndFilled() { + STriangle tri = STriangle.create(0, 0, 20, Color.RED, Color.GREEN); + tri.addAttributes(new ColorAttributes(true, true, Color.RED, Color.GREEN)); + draftman.visitTriangle(tri); + + String svg = output(); + assertTrue(svg.contains("fill:#ff0000"), "Should contain fill style"); + assertTrue(svg.contains("stroke:#00ff00"), "Should contain stroke style"); + } + + @Test + void testTriangleNotFilled() { + STriangle tri = STriangle.create(0, 0, 20, Color.RED, Color.BLACK); + tri.addAttributes(new ColorAttributes(false, true, Color.RED, Color.BLACK)); + draftman.visitTriangle(tri); + + String svg = output(); + assertTrue(svg.contains("fill:none"), "Should have fill:none when not filled"); + } + } + + // ── visitText ─────────────────────────────────────────────────── + + @Nested + class VisitTextTests { + + @Test + void testTextElementWithCorrectContent() { + SText text = SText.create(10, 20, "Hello"); + draftman.visitText(text); + + String svg = output(); + assertTrue(svg.contains(" element"); + assertTrue(svg.contains(">Hello"), "Should contain the text content"); + } + + @Test + void testTextPositionIncludesFontSizeOffset() { + SText text = SText.create(10, 20, "Hi"); + draftman.visitText(text); + + String svg = output(); + assertTrue(svg.contains("x=\"10\""), "x should match bounds.x"); + // y = bounds.y + fontSize = 20 + 16 = 36 + assertTrue(svg.contains("y=\"36\""), "y should be bounds.y + fontSize"); + } + + @Test + void testTextDefaultFontAttributes() { + SText text = SText.create(0, 0, "Test"); + draftman.visitText(text); + + String svg = output(); + assertTrue(svg.contains("font-family=\"SansSerif\""), "Should use default font family"); + assertTrue(svg.contains("font-size=\"16\""), "Should use default font size"); + assertTrue(svg.contains("font-style=\"normal\""), "Default style should be normal"); + assertTrue(svg.contains("font-weight=\"normal\""), "Default weight should be normal"); + } + + @Test + void testTextDefaultColorIsBlack() { + SText text = SText.create(0, 0, "Test"); + // default ColorAttributes: filled=true, filledColor=BLACK + draftman.visitText(text); + + String svg = output(); + assertTrue(svg.contains("fill=\"#000000\""), "Default text color should be black"); + } + + @Test + void testTextWithCustomFillColor() { + SText text = SText.create(0, 0, "Colored"); + text.addAttributes(new ColorAttributes(true, false, Color.RED, Color.BLACK)); + draftman.visitText(text); + + String svg = output(); + assertTrue(svg.contains("fill=\"#ff0000\""), "Text should use filledColor"); + } + + @Test + void testTextWithNullColorAttributesFallsBackToBlack() { + SText text = SText.create(0, 0, "NoColor"); + text.addAttributes(new ColorAttributes(false, false, null, null)); + draftman.visitText(text); + + String svg = output(); + assertTrue(svg.contains("fill=\"#000000\""), "Should fall back to black with null colors"); + } + } + + // ── visitCollection ───────────────────────────────────────────── + + @Nested + class VisitCollectionTests { + + @Test + void testEmptyCollection() { + SCollection empty = SCollection.of(); + draftman.visitCollection(empty); + + String svg = output(); + assertEquals("", svg, "Empty collection should produce no output"); + } + + @Test + void testCollectionVisitsAllChildren() { + SCollection coll = SCollection.of( + SRectangle.create(0, 0, 10, 10), + SCircle.create(20, 20, 5) + ); + draftman.visitCollection(coll); + + String svg = output(); + assertTrue(svg.contains(""), + "Should include XML declaration"); + } + + @Test + void testGenerateSVGIncludesSvgNamespace() { + SCollection model = SCollection.of(); + draftman.generateSVG(model); + + String svg = output(); + assertTrue(svg.contains("xmlns=\"http://www.w3.org/2000/svg\""), + "Should include SVG namespace"); + } + + @Test + void testGenerateSVGClosesSvgTag() { + SCollection model = SCollection.of(); + draftman.generateSVG(model); + + String svg = output(); + assertTrue(svg.contains(""), "Should close tag"); + } + + @Test + void testGenerateSVGIncludesShapeContent() { + SCollection model = SCollection.of( + SRectangle.create(1, 2, 3, 4) + ); + draftman.generateSVG(model); + + String svg = output(); + assertTrue(svg.contains(""), "Should close SVG after shapes"); + } + } +}