1 Commits

Author SHA1 Message Date
ba37b199de fix(html): fix hashCode for triangle
The bug caused HTML class and CSS selector to have different IDs, breaking triangle rendering.
2026-03-27 16:39:10 +01:00
4 changed files with 370 additions and 2 deletions

View File

@@ -66,11 +66,11 @@ public class HTMLDraftman implements ShapeVisitor {
@Override
public void visitTriangle(STriangle sTriangle) {
htmlOutput.printf("<div class=\"triangle%d\"></div>\n", this.hashCode());
htmlOutput.printf("<div class=\"triangle%d\"></div>\n", sTriangle.hashCode());
var bounds = sTriangle.getBounds();
ColorAttributes colAttrs = (ColorAttributes) sTriangle.getAttributes(ColorAttributes.ID);
String colorString = formatCSSColor(colAttrs.filledColor);
StringJoiner joiner = new StringJoiner("\n", ".triangle%d{\n".formatted(this.hashCode()), "\n}\n");
StringJoiner joiner = new StringJoiner("\n", ".triangle%d{\n".formatted(sTriangle.hashCode()), "\n}\n");
joiner.add(" position: absolute;");
joiner.add(" top: %dpx;".formatted(bounds.y));
joiner.add(" left: %dpx;".formatted(bounds.x));

View File

@@ -0,0 +1,144 @@
package ovh.gasser.newshapes;
import org.junit.jupiter.api.Test;
import ovh.gasser.newshapes.attributes.SelectionAttributes;
import ovh.gasser.newshapes.shapes.SCircle;
import ovh.gasser.newshapes.shapes.SRectangle;
import ovh.gasser.newshapes.shapes.STriangle;
import ovh.gasser.newshapes.ui.listeners.SelectionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.*;
class SelectionTest {
@Test
void testIsEmptyInitially() {
Selection selection = new Selection();
assertTrue(selection.isEmpty());
}
@Test
void testAdd() {
Selection selection = new Selection();
SCircle circle = SCircle.create(0, 0, 50);
selection.add(circle);
assertFalse(selection.isEmpty());
assertEquals(1, selection.getSelectedShapes().size());
assertTrue(selection.getSelectedShapes().contains(circle));
// Verify shape is marked as selected
SelectionAttributes attrs = (SelectionAttributes) circle.getAttributes(SelectionAttributes.ID);
assertNotNull(attrs);
assertTrue(attrs.selected);
}
@Test
void testAddAll() {
Selection selection = new Selection();
SCircle circle1 = SCircle.create(0, 0, 50);
SRectangle rect = SRectangle.create(10, 10, 100, 50);
STriangle triangle = STriangle.create(50, 50, 30, java.awt.Color.BLACK, java.awt.Color.BLACK);
List<ovh.gasser.newshapes.shapes.Shape> shapes = Arrays.asList(circle1, rect, triangle);
selection.addAll(shapes);
assertEquals(3, selection.getSelectedShapes().size());
assertTrue(selection.getSelectedShapes().contains(circle1));
assertTrue(selection.getSelectedShapes().contains(rect));
assertTrue(selection.getSelectedShapes().contains(triangle));
}
@Test
void testClear() {
Selection selection = new Selection();
SCircle circle = SCircle.create(0, 0, 50);
SRectangle rect = SRectangle.create(10, 10, 100, 50);
selection.add(circle);
selection.add(rect);
assertFalse(selection.isEmpty());
assertEquals(2, selection.getSelectedShapes().size());
selection.clear();
assertTrue(selection.isEmpty());
assertEquals(0, selection.getSelectedShapes().size());
// Verify shapes are marked as unselected
SelectionAttributes circleAttrs = (SelectionAttributes) circle.getAttributes(SelectionAttributes.ID);
SelectionAttributes rectAttrs = (SelectionAttributes) rect.getAttributes(SelectionAttributes.ID);
assertNotNull(circleAttrs);
assertNotNull(rectAttrs);
assertFalse(circleAttrs.selected);
assertFalse(rectAttrs.selected);
}
@Test
void testGetSelectedShapesReturnsCopy() {
Selection selection = new Selection();
SCircle circle = SCircle.create(0, 0, 50);
selection.add(circle);
List<ovh.gasser.newshapes.shapes.Shape> copy = selection.getSelectedShapes();
// Try to modify the copy
assertThrows(UnsupportedOperationException.class, () -> copy.add(SCircle.create(10, 10, 20)));
// Original should be unchanged
assertEquals(1, selection.getSelectedShapes().size());
}
@Test
void testAddListener() {
Selection selection = new Selection();
AtomicBoolean listenerNotified = new AtomicBoolean(false);
AtomicInteger notificationCount = new AtomicInteger(0);
SelectionListener listener = (selectedShapes) -> {
listenerNotified.set(true);
notificationCount.incrementAndGet();
};
selection.addListener(listener);
// Adding a shape should notify listener
SCircle circle = SCircle.create(0, 0, 50);
selection.add(circle);
assertTrue(listenerNotified.get());
assertEquals(1, notificationCount.get());
// Adding another shape should notify listener again
SRectangle rect = SRectangle.create(10, 10, 100, 50);
selection.add(rect);
assertEquals(2, notificationCount.get());
// Clearing should notify listener
selection.clear();
assertEquals(3, notificationCount.get());
}
@Test
void testNullAddAllDoesNothing() {
Selection selection = new Selection();
// Should not throw
assertDoesNotThrow(() -> selection.addAll(null));
// Selection should still be empty
assertTrue(selection.isEmpty());
}
}

View File

@@ -0,0 +1,176 @@
package ovh.gasser.newshapes.shapes;
import org.junit.jupiter.api.Test;
import ovh.gasser.newshapes.App;
import ovh.gasser.newshapes.attributes.ColorAttributes;
import ovh.gasser.newshapes.attributes.SelectionAttributes;
import java.awt.Color;
import java.awt.Rectangle;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.*;
class SCollectionTest {
@Test
void testCreateWithShapes() {
SRectangle rect = SRectangle.create(10, 20, 100, 50);
SCircle circle = SCircle.create(50, 50, 25);
SCollection collection = SCollection.of(rect, circle);
assertNotNull(collection);
assertEquals(2, collection.stream().count());
}
@Test
void testAdd() {
SCollection collection = SCollection.of();
SRectangle rect = SRectangle.create(10, 20, 100, 50);
collection.add(rect);
assertEquals(1, collection.stream().count());
assertSame(rect, collection.iterator().next());
}
@Test
void testRemove() {
SRectangle rect = SRectangle.create(10, 20, 100, 50);
SCollection collection = SCollection.of(rect);
collection.remove(rect);
assertEquals(0, collection.stream().count());
}
@Test
void testIterator() {
SRectangle rect1 = SRectangle.create(10, 20, 100, 50);
SRectangle rect2 = SRectangle.create(30, 40, 60, 70);
SCollection collection = SCollection.of(rect1, rect2);
Iterator<Shape> iterator = collection.iterator();
assertTrue(iterator.hasNext());
assertSame(rect1, iterator.next());
assertTrue(iterator.hasNext());
assertSame(rect2, iterator.next());
assertFalse(iterator.hasNext());
}
@Test
void testStream() {
SRectangle rect1 = SRectangle.create(10, 20, 100, 50);
SRectangle rect2 = SRectangle.create(30, 40, 60, 70);
SCircle circle = SCircle.create(50, 50, 25);
SCollection collection = SCollection.of(rect1, rect2, circle);
List<Shape> shapes = collection.stream().collect(Collectors.toList());
assertEquals(3, shapes.size());
assertTrue(shapes.contains(rect1));
assertTrue(shapes.contains(rect2));
assertTrue(shapes.contains(circle));
}
@Test
void testGetBoundsEmptyCollection() {
SCollection collection = SCollection.of();
Rectangle bounds = collection.getBounds();
assertEquals(App.WIN_SIZE.width, bounds.width);
assertEquals(App.WIN_SIZE.height, bounds.height);
}
@Test
void testGetBoundsWithChildren() {
SRectangle rect1 = SRectangle.create(10, 10, 50, 50);
SRectangle rect2 = SRectangle.create(100, 100, 80, 40);
SCollection collection = SCollection.of(rect1, rect2);
Rectangle bounds = collection.getBounds();
// Union should cover from (10,10) to (180,140)
assertEquals(10, bounds.x);
assertEquals(10, bounds.y);
assertEquals(170, bounds.width); // 100+80-10 = 170
assertEquals(130, bounds.height); // 100+40-10 = 130
}
@Test
void testTranslate() {
SRectangle rect = SRectangle.create(10, 20, 100, 50);
SCollection collection = SCollection.of(rect);
collection.translate(5, 10);
Rectangle bounds = rect.getBounds();
assertEquals(15, bounds.x);
assertEquals(30, bounds.y);
}
@Test
void testClone() {
SRectangle rect = SRectangle.create(10, 20, 100, 50);
SCollection original = SCollection.of(rect);
SCollection cloned = (SCollection) original.clone();
assertNotSame(original, cloned);
assertEquals(original.stream().count(), cloned.stream().count());
// Verify SelectionAttributes is added to clone
assertNotNull(cloned.getAttributes(SelectionAttributes.ID));
// Verify deep copy - modifying clone doesn't affect original
Iterator<Shape> clonedIterator = cloned.iterator();
Shape clonedChild = clonedIterator.next();
clonedChild.translate(100, 100);
Rectangle originalBounds = rect.getBounds();
assertEquals(10, originalBounds.x);
assertEquals(20, originalBounds.y);
}
@Test
void testToString() {
SCollection collection = SCollection.of();
String str = collection.toString();
assertTrue(str.contains("SCollection"));
}
@Test
void testAddAttributesPropagatesToChildren() {
SRectangle rect = SRectangle.create(10, 20, 100, 50);
SCollection collection = SCollection.of(rect);
ColorAttributes attrs = new ColorAttributes(true, true, Color.RED, Color.BLUE);
collection.addAttributes(attrs);
ColorAttributes childAttrs = (ColorAttributes) rect.getAttributes(ColorAttributes.ID);
assertNotNull(childAttrs);
assertEquals(Color.RED, childAttrs.filledColor);
assertEquals(Color.BLUE, childAttrs.strokedColor);
}
@Test
void testGetAttributesReturnsChildColor() {
SRectangle rect = SRectangle.create(10, 20, 100, 50, Color.GREEN);
SCircle circle = SCircle.create(50, 50, 25);
SCollection collection = SCollection.of(rect, circle);
ColorAttributes attrs = (ColorAttributes) collection.getAttributes(ColorAttributes.ID);
assertNotNull(attrs);
// First child's color should be returned (strokedColor for SRectangle)
assertEquals(Color.GREEN, attrs.strokedColor);
}
}

View File

@@ -0,0 +1,48 @@
package ovh.gasser.newshapes.util;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
class StreamableTest {
private static class TestStreamable implements Streamable<String> {
private final List<String> elements;
TestStreamable(List<String> elements) {
this.elements = new ArrayList<>(elements);
}
@Override
public java.util.Iterator<String> iterator() {
return elements.iterator();
}
}
@Test
void testStreamReturnsStreamOfElements() {
List<String> testData = Arrays.asList("a", "b", "c");
Streamable<String> streamable = new TestStreamable(testData);
Stream<String> result = streamable.stream();
assertNotNull(result);
assertEquals(testData, result.toList());
}
@Test
void testStreamEmptyCollection() {
List<String> emptyData = new ArrayList<>();
Streamable<String> streamable = new TestStreamable(emptyData);
Stream<String> result = streamable.stream();
assertNotNull(result);
assertTrue(result.toList().isEmpty());
}
}