diff --git a/src/test/java/ovh/gasser/newshapes/shapes/EdgeCaseTest.java b/src/test/java/ovh/gasser/newshapes/shapes/EdgeCaseTest.java new file mode 100644 index 0000000..3ee1790 --- /dev/null +++ b/src/test/java/ovh/gasser/newshapes/shapes/EdgeCaseTest.java @@ -0,0 +1,223 @@ +package ovh.gasser.newshapes.shapes; + +import ovh.gasser.newshapes.App; +import ovh.gasser.newshapes.attributes.SelectionAttributes; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.awt.*; + +import static org.junit.jupiter.api.Assertions.*; + +class EdgeCaseTest { + + // ------------------------------------------------------------------------- + // NegativeCoordinates + // ------------------------------------------------------------------------- + + @Nested + class NegativeCoordinates { + + @Test + void rectangleAtNegativeCoordsShouldPreserveBounds() { + SRectangle rect = SRectangle.create(-10, -20, 50, 50); + Rectangle bounds = rect.getBounds(); + assertEquals(-10, bounds.x, "x should be -10"); + assertEquals(-20, bounds.y, "y should be -20"); + assertEquals(50, bounds.width, "width should be 50"); + assertEquals(50, bounds.height, "height should be 50"); + } + + @Test + void circleAtNegativeCoordsShouldPreserveBounds() { + SCircle circle = SCircle.create(-30, -40, 25); + Rectangle bounds = circle.getBounds(); + assertEquals(-30, bounds.x, "x should be -30"); + assertEquals(-40, bounds.y, "y should be -40"); + assertEquals(50, bounds.width, "width should be radius*2 = 50"); + assertEquals(50, bounds.height, "height should be radius*2 = 50"); + } + + @Test + void triangleAtNegativeCoordsShouldPreserveBounds() { + STriangle tri = STriangle.create(-5, -15, 60, Color.RED, Color.BLACK); + Rectangle bounds = tri.getBounds(); + assertEquals(-5, bounds.x, "x should be -5"); + assertEquals(-15, bounds.y, "y should be -15"); + assertEquals(60, bounds.width, "width should be 60"); + assertEquals(60, bounds.height, "height should be 60"); + } + + @Test + void translateToNegativePositionShouldUpdateBoundsCorrectly() { + SRectangle rect = SRectangle.create(10, 20, 80, 40); + rect.translate(-50, -70); + Rectangle bounds = rect.getBounds(); + assertEquals(-40, bounds.x, "x after translate should be -40"); + assertEquals(-50, bounds.y, "y after translate should be -50"); + // dimensions must be unchanged + assertEquals(80, bounds.width, "width must remain 80 after translate"); + assertEquals(40, bounds.height, "height must remain 40 after translate"); + } + } + + // ------------------------------------------------------------------------- + // ZeroDimensions + // ------------------------------------------------------------------------- + + @Nested + class ZeroDimensions { + + @Test + void rectangleWithZeroWidthShouldReturnZeroWidthFromFactory() { + SRectangle rect = SRectangle.create(0, 0, 0, 50); + assertEquals(0, rect.getBounds().width, + "factory should allow zero width without clamping"); + } + + @Test + void rectangleWithZeroHeightShouldReturnZeroHeightFromFactory() { + SRectangle rect = SRectangle.create(0, 0, 50, 0); + assertEquals(0, rect.getBounds().height, + "factory should allow zero height without clamping"); + } + + @Test + void circleWithZeroRadiusShouldHaveZeroBoundsDimensions() { + SCircle circle = SCircle.create(5, 10, 0); + assertEquals(0, circle.getRadius(), "radius should be 0"); + assertEquals(0, circle.getBounds().width, "bounds width should be 0 (radius*2)"); + assertEquals(0, circle.getBounds().height, "bounds height should be 0 (radius*2)"); + } + } + + // ------------------------------------------------------------------------- + // ResizeClamping + // ------------------------------------------------------------------------- + + @Nested + class ResizeClamping { + + @Test + void rectangleResizedToNegativeShouldClampWidthToOne() { + // Start with a 10x10 rect and drag the E handle far to the left + SRectangle rect = SRectangle.create(0, 0, 10, 10); + rect.resize(ResizeHandle.E, -200, 0); + assertTrue(rect.getBounds().width >= 1, + "width must be clamped to a minimum of 1 after negative resize"); + assertEquals(1, rect.getBounds().width, + "width should be exactly 1 after extreme negative resize on E handle"); + } + + @Test + void rectangleResizedToNegativeShouldClampHeightToOne() { + SRectangle rect = SRectangle.create(0, 0, 10, 10); + rect.resize(ResizeHandle.S, 0, -200); + assertTrue(rect.getBounds().height >= 1, + "height must be clamped to a minimum of 1 after negative resize"); + assertEquals(1, rect.getBounds().height, + "height should be exactly 1 after extreme negative resize on S handle"); + } + + @Test + void circleResizedToNegativeShouldClampToTwo() { + SCircle circle = SCircle.create(0, 0, 50); + // Drag E handle far to the left → shrink + circle.resize(ResizeHandle.E, -500, 0); + assertTrue(circle.getBounds().width >= 2, + "circle width must be clamped to a minimum of 2"); + assertTrue(circle.getBounds().height >= 2, + "circle height must be clamped to a minimum of 2"); + assertEquals(2, circle.getBounds().width, + "circle width should be exactly 2 after extreme negative resize"); + assertEquals(2, circle.getBounds().height, + "circle height should be exactly 2 after extreme negative resize"); + } + } + + // ------------------------------------------------------------------------- + // NullHandling + // ------------------------------------------------------------------------- + + @Nested + class NullHandling { + + @Test + void textCreatedWithNullShouldReturnPlaceholder() { + SText text = SText.create(0, 0, null); + assertEquals(SText.PLACEHOLDER_TEXT, text.getText(), + "null input should be normalized to the placeholder text"); + } + + @Test + void textCreatedWithBlankStringShouldReturnPlaceholder() { + SText text = SText.create(0, 0, " "); + assertEquals(SText.PLACEHOLDER_TEXT, text.getText(), + "blank string input should be normalized to the placeholder text"); + } + + @Test + void getAttributesWithUnknownKeyShouldReturnNull() { + SRectangle rect = SRectangle.create(0, 0, 50, 50); + assertNull(rect.getAttributes("NONEXISTENT"), + "getAttributes() with an unknown key must return null"); + } + + @Test + void emptyCollectionCreationShouldNotThrow() { + assertDoesNotThrow(() -> SCollection.of(), + "SCollection.of() with no arguments must not throw"); + } + + @Test + void emptyCollectionGetBoundsShouldReturnWindowSize() { + SCollection collection = SCollection.of(); + Rectangle bounds = collection.getBounds(); + assertNotNull(bounds, "getBounds() on empty collection must not return null"); + assertEquals(App.WIN_SIZE.width, bounds.width, + "empty collection bounds width should equal WIN_SIZE.width"); + assertEquals(App.WIN_SIZE.height, bounds.height, + "empty collection bounds height should equal WIN_SIZE.height"); + } + } + + // ------------------------------------------------------------------------- + // LargeValues + // ------------------------------------------------------------------------- + + @Nested + class LargeValues { + + @Test + void rectangleAtMaxIntHalfCoordsShouldNotThrow() { + int halfMax = Integer.MAX_VALUE / 2; + assertDoesNotThrow(() -> SRectangle.create(halfMax, halfMax, 100, 100), + "SRectangle.create() with very large coordinates must not throw"); + } + + @Test + void rectangleAtMaxIntHalfCoordsPreservesBounds() { + int halfMax = Integer.MAX_VALUE / 2; + SRectangle rect = SRectangle.create(halfMax, halfMax, 100, 100); + assertEquals(halfMax, rect.getBounds().x, "x should be Integer.MAX_VALUE/2"); + assertEquals(halfMax, rect.getBounds().y, "y should be Integer.MAX_VALUE/2"); + assertEquals(100, rect.getBounds().width, "width should be 100"); + assertEquals(100, rect.getBounds().height, "height should be 100"); + } + + @Test + void translateWithLargeValuesShouldNotThrow() { + SRectangle rect = SRectangle.create(0, 0, 50, 50); + // Integer overflow is a known platform behaviour; we only assert no exception + assertDoesNotThrow(() -> rect.translate(Integer.MAX_VALUE, Integer.MAX_VALUE), + "translate() with Integer.MAX_VALUE must not throw (overflow is acceptable)"); + } + + @Test + void translateWithLargeNegativeValuesShouldNotThrow() { + SRectangle rect = SRectangle.create(0, 0, 50, 50); + assertDoesNotThrow(() -> rect.translate(Integer.MIN_VALUE, Integer.MIN_VALUE), + "translate() with Integer.MIN_VALUE must not throw (overflow is acceptable)"); + } + } +}