From c0b3ce4cd01853942a8d4cd149c0351e96ea9c64 Mon Sep 17 00:00:00 2001 From: tjens23 Date: Sun, 4 Jan 2026 14:06:06 +0100 Subject: [PATCH 1/4] stuff --- jhotdraw-app/pom.xml | 19 + .../jhotdraw/app/internal/ChooserManager.java | 166 +++++++++ .../app/internal/RecentFilesManager.java | 144 ++++++++ .../jhotdraw/app/internal/ViewManager.java | 158 ++++++++ .../app/internal/ViewManagerTest.java | 313 ++++++++++++++++ jhotdraw-core/pom.xml | 35 +- .../draw/figure/AbstractFigureNGTest.java | 141 -------- .../draw/figure/AbstractFigureTest.java | 222 ++++++++++++ .../draw/figure/RectangleFigureTest.java | 341 ++++++++++++++++++ .../draw/figure/bdd/GivenFigureCreation.java | 74 ++++ .../figure/bdd/RectangleFigureBDDTest.java | 124 +++++++ .../draw/figure/bdd/ThenFigureBehavior.java | 112 ++++++ .../figure/bdd/WhenFigureManipulation.java | 94 +++++ .../draw/io/ImageFormatRegistryTest.java | 67 ++-- 14 files changed, 1840 insertions(+), 170 deletions(-) create mode 100644 jhotdraw-app/src/main/java/org/jhotdraw/app/internal/ChooserManager.java create mode 100644 jhotdraw-app/src/main/java/org/jhotdraw/app/internal/RecentFilesManager.java create mode 100644 jhotdraw-app/src/main/java/org/jhotdraw/app/internal/ViewManager.java create mode 100644 jhotdraw-app/src/test/java/org/jhotdraw/app/internal/ViewManagerTest.java delete mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/AbstractFigureNGTest.java create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/AbstractFigureTest.java create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/RectangleFigureTest.java create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/GivenFigureCreation.java create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/RectangleFigureBDDTest.java create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/ThenFigureBehavior.java create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/WhenFigureManipulation.java diff --git a/jhotdraw-app/pom.xml b/jhotdraw-app/pom.xml index a36a532b5..c2a483b51 100644 --- a/jhotdraw-app/pom.xml +++ b/jhotdraw-app/pom.xml @@ -24,5 +24,24 @@ jhotdraw-gui ${project.version} + + + junit + junit + 4.13.2 + test + + + org.mockito + mockito-core + 3.12.4 + test + + + org.assertj + assertj-core + 3.21.0 + test + \ No newline at end of file diff --git a/jhotdraw-app/src/main/java/org/jhotdraw/app/internal/ChooserManager.java b/jhotdraw-app/src/main/java/org/jhotdraw/app/internal/ChooserManager.java new file mode 100644 index 000000000..b326bdbb7 --- /dev/null +++ b/jhotdraw-app/src/main/java/org/jhotdraw/app/internal/ChooserManager.java @@ -0,0 +1,166 @@ + +package org.jhotdraw.app.internal; + +import org.jhotdraw.api.app.Application; +import org.jhotdraw.api.app.ApplicationModel; +import org.jhotdraw.api.app.View; +import org.jhotdraw.api.gui.URIChooser; +public class ChooserManager { + + private URIChooser openChooser; + private URIChooser saveChooser; + private URIChooser importChooser; + private URIChooser exportChooser; + + private final Application application; + private final ApplicationModel model; + + public ChooserManager(Application application, ApplicationModel model) { + this.application = application; + this.model = model; + } + + /** + * Gets an open chooser for the specified view or for the application. + * Implements Lazy Initialization pattern. + */ + public URIChooser getOpenChooser(View v) { + if (v == null) { + return getApplicationOpenChooser(); + } else { + return getViewOpenChooser(v); + } + } + + /** + * Gets a save chooser for the specified view or for the application. + */ + public URIChooser getSaveChooser(View v) { + if (v == null) { + return getApplicationSaveChooser(); + } else { + return getViewSaveChooser(v); + } + } + + /** + * Gets an import chooser for the specified view or for the application. + */ + public URIChooser getImportChooser(View v) { + if (v == null) { + return getApplicationImportChooser(); + } else { + return getViewImportChooser(v); + } + } + + /** + * Gets an export chooser for the specified view or for the application. + */ + public URIChooser getExportChooser(View v) { + if (v == null) { + return getApplicationExportChooser(); + } else { + return getViewExportChooser(v); + } + } + + // Application-level choosers (singletons) + + private URIChooser getApplicationOpenChooser() { + if (openChooser == null) { + openChooser = createAndConfigureChooser(() -> model.createOpenChooser(application, null)); + } + return openChooser; + } + + private URIChooser getApplicationSaveChooser() { + if (saveChooser == null) { + saveChooser = createAndConfigureChooser(() -> model.createSaveChooser(application, null)); + } + return saveChooser; + } + + private URIChooser getApplicationImportChooser() { + if (importChooser == null) { + importChooser = createAndConfigureChooser(() -> model.createImportChooser(application, null)); + } + return importChooser; + } + + private URIChooser getApplicationExportChooser() { + if (exportChooser == null) { + exportChooser = createAndConfigureChooser(() -> model.createExportChooser(application, null)); + } + return exportChooser; + } + + // View-specific choosers + + private URIChooser getViewOpenChooser(View v) { + return getOrCreateViewChooser(v, "openChooser", () -> model.createOpenChooser(application, v)); + } + + private URIChooser getViewSaveChooser(View v) { + return getOrCreateViewChooser(v, "saveChooser", () -> model.createSaveChooser(application, v)); + } + + private URIChooser getViewImportChooser(View v) { + return getOrCreateViewChooser(v, "importChooser", () -> model.createImportChooser(application, v)); + } + + private URIChooser getViewExportChooser(View v) { + return getOrCreateViewChooser(v, "exportChooser", () -> model.createExportChooser(application, v)); + } + + // Helper methods using Strategy pattern for chooser creation + + /** + * Gets or creates a view-specific chooser using the provided factory. + * Implements Template Method pattern for view chooser management. + */ + private URIChooser getOrCreateViewChooser(View v, String clientPropertyKey, ChooserFactory factory) { + URIChooser chooser = (URIChooser) v.getComponent().getClientProperty(clientPropertyKey); + if (chooser == null) { + chooser = factory.createChooser(); + v.getComponent().putClientProperty(clientPropertyKey, chooser); + setupViewChooserProperties(chooser, v); + } + return chooser; + } + + /** + * Creates and configures an application-level chooser. + */ + private URIChooser createAndConfigureChooser(ChooserFactory factory) { + URIChooser chooser = factory.createChooser(); + chooser.getComponent().putClientProperty("application", application); + return chooser; + } + + /** + * Sets up client properties for view-specific choosers. + */ + private void setupViewChooserProperties(URIChooser chooser, View view) { + chooser.getComponent().putClientProperty("view", view); + chooser.getComponent().putClientProperty("application", application); + } + + /** + * Functional interface for chooser creation strategy. + */ + @FunctionalInterface + private interface ChooserFactory { + URIChooser createChooser(); + } + + /** + * Clears all cached choosers - useful for testing or configuration changes. + */ + public void clearCachedChoosers() { + openChooser = null; + saveChooser = null; + importChooser = null; + exportChooser = null; + } +} diff --git a/jhotdraw-app/src/main/java/org/jhotdraw/app/internal/RecentFilesManager.java b/jhotdraw-app/src/main/java/org/jhotdraw/app/internal/RecentFilesManager.java new file mode 100644 index 000000000..b6ba1c430 --- /dev/null +++ b/jhotdraw-app/src/main/java/org/jhotdraw/app/internal/RecentFilesManager.java @@ -0,0 +1,144 @@ +/* + * @(#)RecentFilesManager.java + * + * Copyright (c) 1996-2010 The authors and contributors of JHotDraw. + * You may not use, copy or modify this file, except in compliance with the + * accompanying license terms. + */ +package org.jhotdraw.app.internal; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.LinkedList; +import java.util.List; +import java.util.prefs.Preferences; + +public class RecentFilesManager { + + private static final int MAX_RECENT_FILES_COUNT = 10; + private final LinkedList recentURIs = new LinkedList<>(); + private final Preferences preferences; + + public RecentFilesManager(Preferences preferences) { + this.preferences = preferences; + loadRecentFiles(); + } + + /** + * Loads recent files from preferences using Template Method pattern. + */ + private void loadRecentFiles() { + int count = preferences.getInt("recentFileCount", 0); + for (int i = 0; i < count; i++) { + String path = preferences.get("recentFile." + i, null); + if (path != null) { + try { + addRecentURIInternal(new URI(path)); + } catch (URISyntaxException ex) { + ex.printStackTrace(); + } + } + } + } + + /** + * Saves recent files to preferences. + */ + public void saveRecentFiles() { + preferences.putInt("recentFileCount", recentURIs.size()); + for (int i = 0; i < recentURIs.size(); i++) { + preferences.put("recentFile." + i, recentURIs.get(i).toString()); + } + } + + /** + * Adds a URI to the recent files list. + * Implements the strategy of keeping most recent at the front. + */ + public void addRecentURI(URI uri) { + if (uri == null) { + return; + } + + // Remove if already exists to avoid duplicates + recentURIs.remove(uri); + + // Add to front + recentURIs.addFirst(uri); + + // Limit size + while (recentURIs.size() > MAX_RECENT_FILES_COUNT) { + recentURIs.removeLast(); + } + + saveRecentFiles(); + } + + /** + * Internal method for adding without saving (used during loading). + */ + private void addRecentURIInternal(URI uri) { + if (uri != null && !recentURIs.contains(uri)) { + recentURIs.add(uri); + } + } + + /** + * Gets the list of recent URIs. + * Returns a copy to prevent external modification. + */ + public List getRecentURIs() { + return new LinkedList<>(recentURIs); + } + + /** + * Clears all recent files. + */ + public void clearRecentFiles() { + recentURIs.clear(); + preferences.putInt("recentFileCount", 0); + // Remove all stored recent file entries + for (int i = 0; i < MAX_RECENT_FILES_COUNT; i++) { + preferences.remove("recentFile." + i); + } + } + + /** + * Removes a specific URI from the recent files list. + */ + public boolean removeRecentURI(URI uri) { + boolean removed = recentURIs.remove(uri); + if (removed) { + saveRecentFiles(); + } + return removed; + } + + /** + * Gets the most recent URI, or null if none exists. + */ + public URI getMostRecentURI() { + return recentURIs.isEmpty() ? null : recentURIs.getFirst(); + } + + /** + * Checks if the recent files list is empty. + */ + public boolean isEmpty() { + return recentURIs.isEmpty(); + } + + /** + * Gets the maximum number of recent files that can be stored. + */ + public int getMaxRecentFilesCount() { + return MAX_RECENT_FILES_COUNT; + } + + /** + * Gets the current number of recent files. + */ + public int getRecentFilesCount() { + return recentURIs.size(); + } +} diff --git a/jhotdraw-app/src/main/java/org/jhotdraw/app/internal/ViewManager.java b/jhotdraw-app/src/main/java/org/jhotdraw/app/internal/ViewManager.java new file mode 100644 index 000000000..9b5f917d2 --- /dev/null +++ b/jhotdraw-app/src/main/java/org/jhotdraw/app/internal/ViewManager.java @@ -0,0 +1,158 @@ +package org.jhotdraw.app.internal; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import org.jhotdraw.api.app.Application; +import org.jhotdraw.api.app.ApplicationModel; +import org.jhotdraw.api.app.View; + +public class ViewManager { + + public static final String ACTIVE_VIEW_PROPERTY = "activeView"; + public static final String VIEW_COUNT_PROPERTY = "viewCount"; + + private final LinkedList views = new LinkedList<>(); + private Collection unmodifiableViews; + private View activeView; + private final PropertyChangeSupport propertyChangeSupport; + private final Application application; + private final ApplicationModel model; + + public ViewManager(Application application, ApplicationModel model) { + this.application = application; + this.model = model; + this.propertyChangeSupport = new PropertyChangeSupport(this); + } + + /** + * Sets the active view. Calls deactivate on the previously + * active view, and then calls activate on the given view. + * + * @param newValue Active view, can be null. + */ + public void setActiveView(View newValue) { + View oldValue = activeView; + if (activeView != null) { + activeView.deactivate(); + } + activeView = newValue; + if (activeView != null) { + activeView.activate(); + } + firePropertyChange(ACTIVE_VIEW_PROPERTY, oldValue, newValue); + } + + /** + * Gets the active view. + * + * @return The active view can be null. + */ + public View getActiveView() { + return activeView; + } + + /** + * Adds a view to the application. + * Follows the Single Responsibility Principle by focusing only on view management. + */ + public void add(View v) { + if (v.getApplication() != application) { + int oldCount = views.size(); + views.add(v); + v.setApplication(application); + v.init(); + model.initView(application, v); + firePropertyChange(VIEW_COUNT_PROPERTY, oldCount, views.size()); + } + } + + /** + * Removes a view from the application. + */ + public void remove(View v) { + if (v == getActiveView()) { + setActiveView(null); + } + int oldCount = views.size(); + views.remove(v); + v.setApplication(null); + firePropertyChange(VIEW_COUNT_PROPERTY, oldCount, views.size()); + } + + /** + * Gets an unmodifiable list of all views. + */ + public List getViews() { + return Collections.unmodifiableList(views); + } + + /** + * Gets an unmodifiable collection of all views. + * Uses lazy initialization pattern. + */ + public Collection views() { + if (unmodifiableViews == null) { + unmodifiableViews = Collections.unmodifiableCollection(views); + } + return unmodifiableViews; + } + + /** + * Gets the count of currently managed views. + */ + public int getViewCount() { + return views.size(); + } + + /** + * Checks if a view is currently managed by this manager. + */ + public boolean containsView(View view) { + return views.contains(view); + } + + /** + * Clears all views - used during application shutdown. + */ + public void clearAllViews() { + // Create a copy to avoid concurrent modification + List viewsCopy = new LinkedList<>(views); + for (View view : viewsCopy) { + remove(view); + } + setActiveView(null); + } + + // Property change support methods + public void addPropertyChangeListener(PropertyChangeListener listener) { + propertyChangeSupport.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + propertyChangeSupport.removePropertyChangeListener(listener); + } + + public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { + propertyChangeSupport.addPropertyChangeListener(propertyName, listener); + } + + public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { + propertyChangeSupport.removePropertyChangeListener(propertyName, listener); + } + + protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { + propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue); + } + + protected void firePropertyChange(String propertyName, int oldValue, int newValue) { + propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue); + } + + protected void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { + propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue); + } +} diff --git a/jhotdraw-app/src/test/java/org/jhotdraw/app/internal/ViewManagerTest.java b/jhotdraw-app/src/test/java/org/jhotdraw/app/internal/ViewManagerTest.java new file mode 100644 index 000000000..a30d86b69 --- /dev/null +++ b/jhotdraw-app/src/test/java/org/jhotdraw/app/internal/ViewManagerTest.java @@ -0,0 +1,313 @@ +package org.jhotdraw.app.internal; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Collection; +import java.util.List; +import org.jhotdraw.api.app.Application; +import org.jhotdraw.api.app.ApplicationModel; +import org.jhotdraw.api.app.View; +import org.junit.*; +import static org.assertj.core.api.Assertions.*; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import static org.mockito.Mockito.*; + +public class ViewManagerTest { + + @Mock + private Application mockApplication; + + @Mock + private ApplicationModel mockModel; + + @Mock + private View mockView1; + + @Mock + private View mockView2; + + @Mock + private PropertyChangeListener mockPropertyListener; + + private ViewManager viewManager; + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + MockitoAnnotations.openMocks(this); + viewManager = new ViewManager(mockApplication, mockModel); + + // Setup mock behavior + when(mockView1.getApplication()).thenReturn(null).thenReturn(mockApplication); + when(mockView2.getApplication()).thenReturn(null).thenReturn(mockApplication); + } + + @After + public void tearDown() { + viewManager = null; + } + + // BASIC FUNCTIONALITY TESTS + + /** + * Test initial state of ViewManager. + */ + @Test + public void testInitialState() { + assertThat(viewManager.getViewCount()).isZero(); + assertThat(viewManager.getActiveView()).isNull(); + assertThat(viewManager.getViews()).isEmpty(); + assertThat(viewManager.views()).isEmpty(); + } + + /** + * Test adding a single view. + */ + @Test + public void testAddView() { + viewManager.addPropertyChangeListener(ViewManager.VIEW_COUNT_PROPERTY, mockPropertyListener); + + viewManager.add(mockView1); + + assertThat(viewManager.getViewCount()).isEqualTo(1); + assertThat(viewManager.getViews()).containsExactly(mockView1); + assertThat(viewManager.containsView(mockView1)).isTrue(); + + // Verify proper initialization sequence + verify(mockView1).setApplication(mockApplication); + verify(mockView1).init(); + verify(mockModel).initView(mockApplication, mockView1); + + // Verify property change event + verify(mockPropertyListener).propertyChange(any(PropertyChangeEvent.class)); + } + + /** + * Test adding multiple views. + */ + @Test + public void testAddMultipleViews() { + viewManager.add(mockView1); + viewManager.add(mockView2); + + assertThat(viewManager.getViewCount()).isEqualTo(2); + assertThat(viewManager.getViews()).containsExactly(mockView1, mockView2); + } + + /** + * Test removing a view. + */ + @Test + public void testRemoveView() { + viewManager.add(mockView1); + viewManager.addPropertyChangeListener(ViewManager.VIEW_COUNT_PROPERTY, mockPropertyListener); + + viewManager.remove(mockView1); + + assertThat(viewManager.getViewCount()).isZero(); + assertThat(viewManager.containsView(mockView1)).isFalse(); + + verify(mockView1).setApplication(null); + verify(mockPropertyListener).propertyChange(any(PropertyChangeEvent.class)); + } + + // ACTIVE VIEW TESTS + + /** + * Test setting and getting active view. + */ + @Test + public void testSetActiveView() { + viewManager.addPropertyChangeListener(ViewManager.ACTIVE_VIEW_PROPERTY, mockPropertyListener); + + viewManager.setActiveView(mockView1); + + assertThat(viewManager.getActiveView()).isEqualTo(mockView1); + verify(mockView1).activate(); + verify(mockPropertyListener).propertyChange(any(PropertyChangeEvent.class)); + } + + /** + * Test changing active view deactivates previous view. + */ + @Test + public void testChangeActiveViewDeactivatesPrevious() { + viewManager.setActiveView(mockView1); + + viewManager.setActiveView(mockView2); + + assertThat(viewManager.getActiveView()).isEqualTo(mockView2); + verify(mockView1).deactivate(); + verify(mockView2).activate(); + } + + /** + * Test removing active view sets active view to null. + */ + @Test + public void testRemoveActiveViewSetsActiveToNull() { + viewManager.add(mockView1); + viewManager.setActiveView(mockView1); + + viewManager.remove(mockView1); + + assertThat(viewManager.getActiveView()).isNull(); + } + + /** + * Test setting active view to null deactivates current view. + */ + @Test + public void testSetActiveViewToNull() { + viewManager.setActiveView(mockView1); + + viewManager.setActiveView(null); + + assertThat(viewManager.getActiveView()).isNull(); + verify(mockView1).deactivate(); + } + + // EDGE CASES AND ERROR CONDITIONS + + /** + * Test adding same view twice does nothing. + */ + @Test + public void testAddSameViewTwiceDoesNothing() { + when(mockView1.getApplication()).thenReturn(mockApplication); + + viewManager.add(mockView1); + viewManager.add(mockView1); // Second add should do nothing + + assertThat(viewManager.getViewCount()).isEqualTo(1); + verify(mockView1, times(1)).setApplication(mockApplication); + verify(mockView1, times(1)).init(); + } + + /** + * Test removing view that is not managed. + */ + @Test + public void testRemoveUnmanagedView() { + int initialCount = viewManager.getViewCount(); + + viewManager.remove(mockView1); + + assertThat(viewManager.getViewCount()).isEqualTo(initialCount); + } + + /** + * Test clearing all views. + */ + @Test + public void testClearAllViews() { + viewManager.add(mockView1); + viewManager.add(mockView2); + viewManager.setActiveView(mockView1); + + viewManager.clearAllViews(); + + assertThat(viewManager.getViewCount()).isZero(); + assertThat(viewManager.getActiveView()).isNull(); + verify(mockView1).setApplication(null); + verify(mockView2).setApplication(null); + } + + // COLLECTION IMMUTABILITY TESTS + + /** + * Test that getViews returns immutable list. + */ + @Test + public void testGetViewsReturnsImmutableList() { + viewManager.add(mockView1); + List views = viewManager.getViews(); + + assertThatThrownBy(() -> views.add(mockView2)) + .isInstanceOf(UnsupportedOperationException.class); + } + + /** + * Test that views() returns immutable collection. + */ + @Test + public void testViewsReturnsImmutableCollection() { + viewManager.add(mockView1); + Collection views = viewManager.views(); + + assertThatThrownBy(() -> views.add(mockView2)) + .isInstanceOf(UnsupportedOperationException.class); + } + + // PROPERTY CHANGE TESTS + + /** + * Test property change listener registration and removal. + */ + @Test + public void testPropertyChangeListenerManagement() { + viewManager.addPropertyChangeListener(mockPropertyListener); + viewManager.removePropertyChangeListener(mockPropertyListener); + + viewManager.setActiveView(mockView1); + + // Should not receive events after removal + verifyNoInteractions(mockPropertyListener); + } + + /** + * Test property-specific listener registration. + */ + @Test + public void testPropertySpecificListener() { + viewManager.addPropertyChangeListener(ViewManager.ACTIVE_VIEW_PROPERTY, mockPropertyListener); + + viewManager.add(mockView1); // Should not trigger listener + viewManager.setActiveView(mockView1); // Should trigger listener + + verify(mockPropertyListener, times(1)).propertyChange(any(PropertyChangeEvent.class)); + } + + // JAVA ASSERTIONS FOR INVARIANTS + + /** + * Test invariant: active view must be in the views collection or null. + */ + @Test + public void testActiveViewInvariant() { + viewManager.add(mockView1); + viewManager.setActiveView(mockView1); + + assert viewManager.getActiveView() == null || viewManager.containsView(viewManager.getActiveView()) + : "Active view must be in views collection or null"; + + viewManager.remove(mockView1); + assert viewManager.getActiveView() == null + : "Active view should be null after removing it from collection"; + } + + /** + * Test invariant: view count should match actual collection size. + */ + @Test + public void testViewCountInvariant() { + assert viewManager.getViewCount() == viewManager.getViews().size() + : "View count should match collection size"; + + viewManager.add(mockView1); + assert viewManager.getViewCount() == viewManager.getViews().size() + : "View count should match collection size after add"; + + viewManager.remove(mockView1); + assert viewManager.getViewCount() == viewManager.getViews().size() + : "View count should match collection size after remove"; + } +} diff --git a/jhotdraw-core/pom.xml b/jhotdraw-core/pom.xml index 7c276da85..866c102bc 100644 --- a/jhotdraw-core/pom.xml +++ b/jhotdraw-core/pom.xml @@ -29,10 +29,39 @@ jhotdraw-datatransfer ${project.version} + - org.testng - testng - 6.8.21 + junit + junit + 4.13.2 + test + + + + org.mockito + mockito-core + 3.12.4 + test + + + + com.tngtech.jgiven + jgiven-junit + 1.2.5 + test + + + + org.assertj + assertj-core + 3.21.0 + test + + + + org.assertj + assertj-swing-junit + 3.17.1 test diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/AbstractFigureNGTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/AbstractFigureNGTest.java deleted file mode 100644 index 91daec493..000000000 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/AbstractFigureNGTest.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2015 JHotDraw. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ -package org.jhotdraw.draw.figure; - -import java.awt.Graphics2D; -import java.awt.geom.AffineTransform; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.util.Map; -import org.jhotdraw.draw.AttributeKey; -import static org.testng.Assert.*; -import org.testng.annotations.AfterClass; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -/** - * - * @author tw - */ -public class AbstractFigureNGTest { - - public AbstractFigureNGTest() { - } - - @BeforeClass - public static void setUpClass() throws Exception { - } - - @AfterClass - public static void tearDownClass() throws Exception { - } - - @BeforeMethod - public void setUpMethod() throws Exception { - } - - @AfterMethod - public void tearDownMethod() throws Exception { - } - - @Test(expectedExceptions = IllegalStateException.class) - public void testChangedWithoutWillChange() { - new AbstractFigureImpl().changed(); - } - - @Test - public void testWillChangeChangedEvents() { - AbstractFigure figure = new AbstractFigureImpl(); - assertEquals(figure.getChangingDepth(), 0); - figure.willChange(); - assertEquals(figure.getChangingDepth(), 1); - figure.willChange(); - assertEquals(figure.getChangingDepth(), 2); - figure.changed(); - assertEquals(figure.getChangingDepth(), 1); - figure.changed(); - assertEquals(figure.getChangingDepth(), 0); - } - - public class AbstractFigureImpl extends AbstractFigure { - - @Override - public void draw(Graphics2D g) { - } - - @Override - public Rectangle2D.Double getBounds() { - return null; - } - - @Override - public Rectangle2D.Double getDrawingArea() { - return null; - } - - @Override - public boolean contains(Point2D.Double p) { - return true; - } - - @Override - public Object getTransformRestoreData() { - return null; - } - - @Override - public void restoreTransformTo(Object restoreData) { - } - - @Override - public void transform(AffineTransform tx) { - } - - @Override - public void set(AttributeKey key, T value) { - } - - @Override - public T get(AttributeKey key) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public Map, Object> getAttributes() { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public Object getAttributesRestoreData() { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public void restoreAttributesTo(Object restoreData) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - - @Override - public Rectangle2D.Double getDrawingArea(double factor) { - return null; - } - } -} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/AbstractFigureTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/AbstractFigureTest.java new file mode 100644 index 000000000..605d7f92b --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/AbstractFigureTest.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2015 JHotDraw. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.jhotdraw.draw.figure; + +import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.Map; +import org.jhotdraw.draw.AttributeKey; +import org.junit.*; +import static org.assertj.core.api.Assertions.*; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for AbstractFigure using JUnit 4 and AssertJ. + * Tests focus on core figure behavior, attribute management, and geometric operations. + * + * @author tw + */ +public class AbstractFigureTest { + + @Mock + private Graphics2D mockGraphics; + + private TestFigure figure; + + @BeforeClass + public static void setUpClass() throws Exception { + } + + @AfterClass + public static void tearDownClass() throws Exception { + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + figure = new TestFigure(); + } + + @After + public void tearDown() throws Exception { + } + + /** + * Test that calling changed() without willChange() throws exception. + * This is a crucial invariant that should never be violated. + */ + @Test(expected = IllegalStateException.class) + public void testChangedWithoutWillChange() { + new TestFigure().changed(); + } + + /** + * Test the willChange/changed event depth tracking. + * Validates proper nesting of change events. + */ + @Test + public void testWillChangeChangedEvents() { + TestFigure testFigure = new TestFigure(); + + // Initially no changes are pending + assertThat(testFigure.getChangingDepth()).isZero(); + + // First willChange call + testFigure.willChange(); + assertThat(testFigure.getChangingDepth()).isEqualTo(1); + + // Nested willChange call + testFigure.willChange(); + assertThat(testFigure.getChangingDepth()).isEqualTo(2); + + // First changed call + testFigure.changed(); + assertThat(testFigure.getChangingDepth()).isEqualTo(1); + + // Final changed call + testFigure.changed(); + assertThat(testFigure.getChangingDepth()).isZero(); + } + + /** + * Test boundary condition: multiple nested willChange calls. + */ + @Test + public void testDeepNestedWillChangeEvents() { + TestFigure testFigure = new TestFigure(); + + // Create deep nesting + for (int i = 1; i <= 10; i++) { + testFigure.willChange(); + assertThat(testFigure.getChangingDepth()).isEqualTo(i); + } + + // Unwind the nesting + for (int i = 9; i >= 0; i--) { + testFigure.changed(); + assertThat(testFigure.getChangingDepth()).isEqualTo(i); + } + } + + /** + * Test figure drawing behavior with mock graphics context. + */ + @Test + public void testDrawWithMockGraphics() { + // This test demonstrates mocking for isolating drawing behavior + figure.draw(mockGraphics); + // In a real implementation, we would verify specific drawing calls + // For now, just ensure no exceptions are thrown + assertThat(figure).isNotNull(); + } + + /** + * Test figure bounds calculation - edge case with null bounds. + */ + @Test + public void testGetBoundsReturnsNull() { + Rectangle2D.Double bounds = figure.getBounds(); + assertThat(bounds).isNull(); // Current implementation returns null + } + + /** + * Test figure containment - current implementation always returns true. + */ + @Test + public void testContainsPoint() { + Point2D.Double point = new Point2D.Double(10, 10); + boolean contains = figure.contains(point); + assertThat(contains).isTrue(); // Current implementation + } + + /** + * Test helper class that extends AbstractFigure for testing purposes. + * This class provides minimal implementations needed for testing. + */ + private static class TestFigure extends AbstractFigure { + + @Override + public void draw(Graphics2D g) { + // Minimal implementation for testing + } + + @Override + public Rectangle2D.Double getBounds() { + return null; // Simplified for testing + } + + @Override + public Rectangle2D.Double getDrawingArea() { + return null; // Simplified for testing + } + + @Override + public boolean contains(Point2D.Double p) { + return true; // Simplified for testing + } + + @Override + public Object getTransformRestoreData() { + return null; // Simplified for testing + } + + @Override + public void restoreTransformTo(Object restoreData) { + // Minimal implementation for testing + } + + @Override + public void transform(AffineTransform tx) { + // Minimal implementation for testing + } + + @Override + public void set(AttributeKey key, T value) { + // Minimal implementation for testing + } + + @Override + public T get(AttributeKey key) { + return null; // Simplified for testing + } + + @Override + public Map, Object> getAttributes() { + return java.util.Collections.emptyMap(); // Simplified for testing + } + + @Override + public Object getAttributesRestoreData() { + return null; // Simplified for testing + } + + @Override + public void restoreAttributesTo(Object restoreData) { + // Minimal implementation for testing + } + + @Override + public Rectangle2D.Double getDrawingArea(double factor) { + return null; // Simplified for testing + } + } +} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/RectangleFigureTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/RectangleFigureTest.java new file mode 100644 index 000000000..1e2175e37 --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/RectangleFigureTest.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2015 JHotDraw. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.jhotdraw.draw.figure; + +import java.awt.Graphics2D; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import org.junit.*; +import static org.assertj.core.api.Assertions.*; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import static org.mockito.Mockito.*; + +/** + * Comprehensive unit tests for RectangleFigure using JUnit 4, AssertJ, and Mockito. + * Tests focus on geometric operations, boundary conditions, and drawing behavior. + * + * @author JHotDraw Team + */ +public class RectangleFigureTest { + + @Mock + private Graphics2D mockGraphics; + + private RectangleFigure rectangle; + + @BeforeClass + public static void setUpClass() { + // Class-level setup if needed + } + + @AfterClass + public static void tearDownClass() { + // Class-level cleanup if needed + } + + @Before + public void setUp() { + MockitoAnnotations.openMocks(this); + rectangle = new RectangleFigure(); + } + + @After + public void tearDown() { + rectangle = null; + } + + // CONSTRUCTOR TESTS + + /** + * Test default constructor creates rectangle at origin with zero dimensions. + */ + @Test + public void testDefaultConstructor() { + RectangleFigure rect = new RectangleFigure(); + Rectangle2D.Double bounds = rect.getBounds(); + + assertThat(bounds.x).isZero(); + assertThat(bounds.y).isZero(); + assertThat(bounds.width).isZero(); + assertThat(bounds.height).isZero(); + } + + /** + * Test parameterized constructor sets dimensions correctly. + */ + @Test + public void testParameterizedConstructor() { + RectangleFigure rect = new RectangleFigure(10, 20, 100, 50); + Rectangle2D.Double bounds = rect.getBounds(); + + assertThat(bounds.x).isEqualTo(10.0); + assertThat(bounds.y).isEqualTo(20.0); + assertThat(bounds.width).isEqualTo(100.0); + assertThat(bounds.height).isEqualTo(50.0); + } + + // BOUNDARY TESTS + + /** + * Test setBounds with normal positive values. + */ + @Test + public void testSetBounds() { + Point2D.Double anchor = new Point2D.Double(10, 10); + Point2D.Double lead = new Point2D.Double(50, 30); + + rectangle.setBounds(anchor, lead); + Rectangle2D.Double bounds = rectangle.getBounds(); + + assertThat(bounds.x).isEqualTo(10.0); + assertThat(bounds.y).isEqualTo(10.0); + assertThat(bounds.width).isEqualTo(40.0); + assertThat(bounds.height).isEqualTo(20.0); + } + + /** + * Test setBounds with inverted anchor and lead points (lead before anchor). + */ + @Test + public void testSetBoundsInvertedPoints() { + Point2D.Double anchor = new Point2D.Double(50, 30); + Point2D.Double lead = new Point2D.Double(10, 10); + + rectangle.setBounds(anchor, lead); + Rectangle2D.Double bounds = rectangle.getBounds(); + + // Should normalize to proper rectangle + assertThat(bounds.x).isEqualTo(10.0); + assertThat(bounds.y).isEqualTo(10.0); + assertThat(bounds.width).isEqualTo(40.0); + assertThat(bounds.height).isEqualTo(20.0); + } + + /** + * Test setBounds with zero-width rectangle enforces minimum width. + */ + @Test + public void testSetBoundsZeroWidth() { + Point2D.Double anchor = new Point2D.Double(10, 10); + Point2D.Double lead = new Point2D.Double(10, 30); // Same x-coordinate + + rectangle.setBounds(anchor, lead); + Rectangle2D.Double bounds = rectangle.getBounds(); + + assertThat(bounds.width).isEqualTo(0.1); // Minimum width enforced + assertThat(bounds.height).isEqualTo(20.0); + } + + /** + * Test setBounds with zero-height rectangle enforces minimum height. + */ + @Test + public void testSetBoundsZeroHeight() { + Point2D.Double anchor = new Point2D.Double(10, 10); + Point2D.Double lead = new Point2D.Double(30, 10); // Same y-coordinate + + rectangle.setBounds(anchor, lead); + Rectangle2D.Double bounds = rectangle.getBounds(); + + assertThat(bounds.width).isEqualTo(20.0); + assertThat(bounds.height).isEqualTo(0.1); // Minimum height enforced + } + + // CONTAINMENT TESTS + + /** + * Test contains method for point inside rectangle. + */ + @Test + public void testContainsPointInside() { + RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); + Point2D.Double insidePoint = new Point2D.Double(20, 17); + + assertThat(rect.contains(insidePoint)).isTrue(); + } + + /** + * Test contains method for point outside rectangle. + */ + @Test + public void testContainsPointOutside() { + RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); + Point2D.Double outsidePoint = new Point2D.Double(5, 5); + + assertThat(rect.contains(outsidePoint)).isFalse(); + } + + /** + * Test contains method for point on rectangle boundary. + */ + @Test + public void testContainsPointOnBoundary() { + RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); + Point2D.Double boundaryPoint = new Point2D.Double(10, 10); // Top-left corner + + // Due to hit growth, boundary points should be contained + assertThat(rect.contains(boundaryPoint)).isTrue(); + } + + // TRANSFORMATION TESTS + + /** + * Test transform with identity transformation. + */ + @Test + public void testTransformIdentity() { + RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); + Rectangle2D.Double originalBounds = rect.getBounds(); + + AffineTransform identity = new AffineTransform(); + rect.transform(identity); + + Rectangle2D.Double newBounds = rect.getBounds(); + assertThat(newBounds).isEqualTo(originalBounds); + } + + /** + * Test transform with translation. + */ + @Test + public void testTransformTranslation() { + RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); + + AffineTransform translation = AffineTransform.getTranslateInstance(5, -3); + rect.transform(translation); + + Rectangle2D.Double bounds = rect.getBounds(); + assertThat(bounds.x).isEqualTo(15.0); + assertThat(bounds.y).isEqualTo(7.0); + assertThat(bounds.width).isEqualTo(20.0); + assertThat(bounds.height).isEqualTo(15.0); + } + + /** + * Test transform with scaling. + */ + @Test + public void testTransformScaling() { + RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); + + AffineTransform scaling = AffineTransform.getScaleInstance(2.0, 0.5); + rect.transform(scaling); + + Rectangle2D.Double bounds = rect.getBounds(); + assertThat(bounds.x).isEqualTo(20.0); + assertThat(bounds.y).isEqualTo(5.0); + assertThat(bounds.width).isEqualTo(40.0); + assertThat(bounds.height).isEqualTo(7.5); + } + + // DRAWING AREA TESTS + + /** + * Test that drawing area is larger than bounds due to stroke growth. + */ + @Test + public void testGetDrawingAreaLargerThanBounds() { + RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); + + Rectangle2D.Double bounds = rect.getBounds(); + Rectangle2D.Double drawingArea = rect.getDrawingArea(); + + assertThat(drawingArea.width).isGreaterThan(bounds.width); + assertThat(drawingArea.height).isGreaterThan(bounds.height); + } + + // DRAWING BEHAVIOR TESTS (with mocking) + + /** + * Test that draw method calls appropriate Graphics2D methods. + * Note: This is a simplified test - in reality we'd need to set up proper attributes. + */ + @Test + public void testDrawCallsGraphicsMethods() { + RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); + + // Create a real graphics context for testing + BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = image.createGraphics(); + + // This should not throw any exceptions + assertThatCode(() -> rect.draw(g2d)).doesNotThrowAnyException(); + + g2d.dispose(); + } + + // EDGE CASES AND ERROR CONDITIONS + + /** + * Test drawing with null graphics context should throw NPE. + */ + @Test + public void testDrawWithNullGraphics() { + RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); + + // Should throw NullPointerException - this is the correct behavior + assertThatThrownBy(() -> rect.draw(null)) + .isInstanceOf(NullPointerException.class); + } + + /** + * Test negative dimensions behavior. + */ + @Test + public void testNegativeDimensions() { + RectangleFigure rect = new RectangleFigure(10, 10, -20, -15); + Rectangle2D.Double bounds = rect.getBounds(); + + // Should handle negative dimensions appropriately + assertThat(bounds.width).isEqualTo(-20.0); // Current behavior - might need adjustment + assertThat(bounds.height).isEqualTo(-15.0); + } + + // JAVA ASSERTIONS FOR INVARIANTS + + /** + * Test invariant: bounds should never be null. + */ + @Test + public void testBoundsInvariant() { + assert rectangle.getBounds() != null : "Bounds should never be null"; + + rectangle.setBounds(new Point2D.Double(0, 0), new Point2D.Double(10, 10)); + assert rectangle.getBounds() != null : "Bounds should never be null after setBounds"; + + rectangle.transform(AffineTransform.getScaleInstance(2, 2)); + assert rectangle.getBounds() != null : "Bounds should never be null after transform"; + } + + /** + * Test invariant: drawing area should always contain bounds. + */ + @Test + public void testDrawingAreaContainsBoundsInvariant() { + RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); + + Rectangle2D.Double bounds = rect.getBounds(); + Rectangle2D.Double drawingArea = rect.getDrawingArea(); + + assert drawingArea.contains(bounds) : "Drawing area should always contain bounds"; + } +} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/GivenFigureCreation.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/GivenFigureCreation.java new file mode 100644 index 000000000..eda646833 --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/GivenFigureCreation.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015 JHotDraw. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.jhotdraw.draw.figure.bdd; + +import com.tngtech.jgiven.Stage; +import com.tngtech.jgiven.annotation.ExpectedScenarioState; +import com.tngtech.jgiven.annotation.ProvidedScenarioState; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import org.jhotdraw.draw.figure.RectangleFigure; +import static org.assertj.core.api.Assertions.*; + +/** + * JGiven Given stage for BDD testing of figure creation and setup scenarios. + * + * @author JHotDraw Team + */ +public class GivenFigureCreation extends Stage { + + @ProvidedScenarioState + protected RectangleFigure rectangle; + + @ProvidedScenarioState + protected Point2D.Double startPoint; + + @ProvidedScenarioState + protected Point2D.Double endPoint; + + public GivenFigureCreation a_new_rectangle_figure() { + rectangle = new RectangleFigure(); + return self(); + } + + public GivenFigureCreation a_rectangle_figure_with_dimensions(double x, double y, double width, double height) { + rectangle = new RectangleFigure(x, y, width, height); + return self(); + } + + public GivenFigureCreation an_existing_rectangle_at_position(double x, double y) { + rectangle = new RectangleFigure(x, y, 50, 30); // Default size + return self(); + } + + public GivenFigureCreation a_start_point_at(double x, double y) { + startPoint = new Point2D.Double(x, y); + return self(); + } + + public GivenFigureCreation an_end_point_at(double x, double y) { + endPoint = new Point2D.Double(x, y); + return self(); + } + + public GivenFigureCreation a_rectangle_with_zero_dimensions() { + rectangle = new RectangleFigure(10, 10, 0, 0); + return self(); + } +} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/RectangleFigureBDDTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/RectangleFigureBDDTest.java new file mode 100644 index 000000000..f5d1f292a --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/RectangleFigureBDDTest.java @@ -0,0 +1,124 @@ +package org.jhotdraw.draw.figure.bdd; + +import com.tngtech.jgiven.junit.ScenarioTest; +import org.junit.Test; + +public class RectangleFigureBDDTest extends ScenarioTest { + + @Test + public void user_can_create_rectangle_by_dragging() { + given().a_new_rectangle_figure() + .and().a_start_point_at(10, 10) + .and().an_end_point_at(50, 40); + + when().the_user_creates_a_rectangle_by_dragging_from_start_to_end(); + + then().the_rectangle_should_have_position(10, 10) + .and().the_rectangle_should_have_dimensions(40, 30); + } + + @Test + public void user_can_resize_existing_rectangle() { + given().a_rectangle_figure_with_dimensions(10, 10, 50, 30); + + when().the_user_resizes_the_rectangle_to(100, 60); + + then().the_rectangle_should_have_dimensions(100, 60) + .and().the_rectangle_should_be_positioned_at(10, 10); + } + + @Test + public void user_can_move_rectangle_to_new_position() { + given().a_rectangle_figure_with_dimensions(10, 10, 50, 30); + + when().the_user_moves_the_figure_by(20, 15); + + then().the_rectangle_should_have_position(30, 25) + .and().the_rectangle_should_have_dimensions(50, 30); + } + + @Test + public void user_can_scale_rectangle_proportionally() { + given().a_rectangle_figure_with_dimensions(10, 10, 40, 20); + + when().the_user_scales_the_figure_by(2.0, 2.0); + + then().the_rectangle_should_maintain_aspect_ratio_after_uniform_scaling(40, 20, 2.0); + } + + @Test + public void rectangle_enforces_minimum_dimensions_when_dragging() { + given().a_new_rectangle_figure() + .and().a_start_point_at(10, 10) + .and().an_end_point_at(10, 10); // Same point - zero dimensions + + when().the_user_creates_a_rectangle_by_dragging_from_start_to_end(); + + then().the_rectangle_should_enforce_minimum_width(0.1) + .and().the_rectangle_should_enforce_minimum_height(0.1); + } + + @Test + public void rectangle_normalizes_bounds_when_dragging_backwards() { + given().a_new_rectangle_figure() + .and().a_start_point_at(50, 40) // Start from bottom-right + .and().an_end_point_at(10, 10); // End at top-left + + when().the_user_creates_a_rectangle_by_dragging_from_start_to_end(); + + then().the_rectangle_should_have_position(10, 10) // Should normalize to top-left + .and().the_rectangle_should_have_dimensions(40, 30) + .and().the_rectangle_should_be_normalized(); + } + + @Test + public void point_inside_rectangle_is_detected_correctly() { + given().a_rectangle_figure_with_dimensions(10, 10, 40, 30); + + when().checking_if_point_$_$_is_contained(25, 20); // Point inside + + then().the_point_should_be_contained(); + } + + @Test + public void point_outside_rectangle_is_detected_correctly() { + given().a_rectangle_figure_with_dimensions(10, 10, 40, 30); + + when().checking_if_point_$_$_is_contained(5, 5); // Point outside + + then().the_point_should_not_be_contained(); + } + + @Test + public void rectangle_drawing_area_accommodates_stroke_width() { + given().a_rectangle_figure_with_dimensions(10, 10, 40, 30); + + when().the_user_moves_the_figure_by(0, 0); // Trigger bounds calculation + + then().the_drawing_area_should_be_larger_than_bounds(); + } + + @Test + public void user_can_create_thin_horizontal_line() { + given().a_new_rectangle_figure() + .and().a_start_point_at(10, 20) + .and().an_end_point_at(50, 20); // Same y-coordinate + + when().the_user_creates_a_rectangle_by_dragging_from_start_to_end(); + + then().the_rectangle_should_have_position(10, 20) + .and().the_rectangle_should_have_dimensions(40, 0.1); // Minimum height enforced + } + + @Test + public void user_can_create_thin_vertical_line() { + given().a_new_rectangle_figure() + .and().a_start_point_at(15, 10) + .and().an_end_point_at(15, 40); // Same x-coordinate + + when().the_user_creates_a_rectangle_by_dragging_from_start_to_end(); + + then().the_rectangle_should_have_position(15, 10) + .and().the_rectangle_should_have_dimensions(0.1, 30); // Minimum width enforced + } +} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/ThenFigureBehavior.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/ThenFigureBehavior.java new file mode 100644 index 000000000..b9e021f89 --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/ThenFigureBehavior.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2015 JHotDraw. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.jhotdraw.draw.figure.bdd; + +import com.tngtech.jgiven.Stage; +import com.tngtech.jgiven.annotation.ExpectedScenarioState; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import org.jhotdraw.draw.figure.RectangleFigure; +import static org.assertj.core.api.Assertions.*; + +public class ThenFigureBehavior extends Stage { + + @ExpectedScenarioState + protected RectangleFigure rectangle; + + @ExpectedScenarioState + protected Rectangle2D.Double resultingBounds; + + @ExpectedScenarioState + protected boolean containmentResult; + + @ExpectedScenarioState + protected Point2D.Double testPoint; + + public ThenFigureBehavior the_rectangle_should_have_position(double x, double y) { + assertThat(resultingBounds.x).isEqualTo(x); + assertThat(resultingBounds.y).isEqualTo(y); + return self(); + } + + public ThenFigureBehavior the_rectangle_should_have_dimensions(double width, double height) { + assertThat(resultingBounds.width).isEqualTo(width); + assertThat(resultingBounds.height).isEqualTo(height); + return self(); + } + + public ThenFigureBehavior the_rectangle_should_be_positioned_at(double x, double y) { + Rectangle2D.Double bounds = rectangle.getBounds(); + assertThat(bounds.x).isEqualTo(x); + assertThat(bounds.y).isEqualTo(y); + return self(); + } + + public ThenFigureBehavior the_rectangle_should_have_size(double width, double height) { + Rectangle2D.Double bounds = rectangle.getBounds(); + assertThat(bounds.width).isEqualTo(width); + assertThat(bounds.height).isEqualTo(height); + return self(); + } + + public ThenFigureBehavior the_point_should_be_contained() { + assertThat(containmentResult).as("Point %s should be contained in rectangle", testPoint).isTrue(); + return self(); + } + + public ThenFigureBehavior the_point_should_not_be_contained() { + assertThat(containmentResult).as("Point %s should not be contained in rectangle", testPoint).isFalse(); + return self(); + } + + public ThenFigureBehavior the_rectangle_should_enforce_minimum_width(double minimumWidth) { + assertThat(resultingBounds.width).isGreaterThanOrEqualTo(minimumWidth); + return self(); + } + + public ThenFigureBehavior the_rectangle_should_enforce_minimum_height(double minimumHeight) { + assertThat(resultingBounds.height).isGreaterThanOrEqualTo(minimumHeight); + return self(); + } + + public ThenFigureBehavior the_rectangle_should_be_normalized() { + // A normalized rectangle should have positive width and height + assertThat(resultingBounds.width).isGreaterThanOrEqualTo(0); + assertThat(resultingBounds.height).isGreaterThanOrEqualTo(0); + return self(); + } + + public ThenFigureBehavior the_drawing_area_should_be_larger_than_bounds() { + Rectangle2D.Double bounds = rectangle.getBounds(); + Rectangle2D.Double drawingArea = rectangle.getDrawingArea(); + + assertThat(drawingArea.width).isGreaterThan(bounds.width); + assertThat(drawingArea.height).isGreaterThan(bounds.height); + return self(); + } + + public ThenFigureBehavior the_rectangle_should_maintain_aspect_ratio_after_uniform_scaling(double originalWidth, double originalHeight, double scaleFactor) { + double expectedWidth = originalWidth * scaleFactor; + double expectedHeight = originalHeight * scaleFactor; + + assertThat(resultingBounds.width).isCloseTo(expectedWidth, within(0.01)); + assertThat(resultingBounds.height).isCloseTo(expectedHeight, within(0.01)); + return self(); + } +} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/WhenFigureManipulation.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/WhenFigureManipulation.java new file mode 100644 index 000000000..ec90f3f68 --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/WhenFigureManipulation.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2015 JHotDraw. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.jhotdraw.draw.figure.bdd; + +import com.tngtech.jgiven.Stage; +import com.tngtech.jgiven.annotation.ExpectedScenarioState; +import com.tngtech.jgiven.annotation.ProvidedScenarioState; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import org.jhotdraw.draw.figure.RectangleFigure; + +/** + * JGiven When stage for BDD testing of figure manipulation actions. + * + * @author JHotDraw Team + */ +public class WhenFigureManipulation extends Stage { + + @ExpectedScenarioState + protected RectangleFigure rectangle; + + @ExpectedScenarioState + protected Point2D.Double startPoint; + + @ExpectedScenarioState + protected Point2D.Double endPoint; + + @ProvidedScenarioState + protected Rectangle2D.Double resultingBounds; + + @ProvidedScenarioState + protected boolean containmentResult; + + @ProvidedScenarioState + protected Point2D.Double testPoint; + + public WhenFigureManipulation the_user_sets_bounds_from_start_to_end_point() { + rectangle.setBounds(startPoint, endPoint); + resultingBounds = rectangle.getBounds(); + return self(); + } + + public WhenFigureManipulation the_user_moves_the_figure_by(double deltaX, double deltaY) { + AffineTransform translation = AffineTransform.getTranslateInstance(deltaX, deltaY); + rectangle.transform(translation); + resultingBounds = rectangle.getBounds(); + return self(); + } + + public WhenFigureManipulation the_user_scales_the_figure_by(double scaleX, double scaleY) { + AffineTransform scaling = AffineTransform.getScaleInstance(scaleX, scaleY); + rectangle.transform(scaling); + resultingBounds = rectangle.getBounds(); + return self(); + } + + public WhenFigureManipulation checking_if_point_$_$_is_contained(double x, double y) { + testPoint = new Point2D.Double(x, y); + containmentResult = rectangle.contains(testPoint); + return self(); + } + + public WhenFigureManipulation the_user_resizes_the_rectangle_to(double width, double height) { + Rectangle2D.Double currentBounds = rectangle.getBounds(); + Point2D.Double anchor = new Point2D.Double(currentBounds.x, currentBounds.y); + Point2D.Double lead = new Point2D.Double(currentBounds.x + width, currentBounds.y + height); + rectangle.setBounds(anchor, lead); + resultingBounds = rectangle.getBounds(); + return self(); + } + + public WhenFigureManipulation the_user_creates_a_rectangle_by_dragging_from_start_to_end() { + rectangle.setBounds(startPoint, endPoint); + resultingBounds = rectangle.getBounds(); + return self(); + } +} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/ImageFormatRegistryTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/ImageFormatRegistryTest.java index 2108adce2..83da0cbf5 100644 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/ImageFormatRegistryTest.java +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/ImageFormatRegistryTest.java @@ -8,36 +8,41 @@ package org.jhotdraw.draw.io; import org.jhotdraw.draw.figure.ImageFigure; -import org.testng.annotations.Test; +import org.junit.Test; import java.util.List; -import static org.testng.Assert.*; +import static org.assertj.core.api.Assertions.*; /** - * Tests for the modular ImageFormatRegistry. + * Tests for the modular ImageFormatRegistry using JUnit 4 and AssertJ. */ public class ImageFormatRegistryTest { @Test public void testPngFormatSupported() { - assertTrue(ImageFormatRegistry.isFormatSupported("png"), - "PNG format should be supported"); - assertTrue(ImageFormatRegistry.isFormatSupported("PNG"), - "PNG format should be supported (uppercase)"); + assertThat(ImageFormatRegistry.isFormatSupported("png")) + .as("PNG format should be supported") + .isTrue(); + assertThat(ImageFormatRegistry.isFormatSupported("PNG")) + .as("PNG format should be supported (uppercase)") + .isTrue(); } @Test public void testJpegFormatSupported() { - assertTrue(ImageFormatRegistry.isFormatSupported("jpg"), - "JPG format should be supported"); - assertTrue(ImageFormatRegistry.isFormatSupported("jpeg"), - "JPEG format should be supported"); + assertThat(ImageFormatRegistry.isFormatSupported("jpg")) + .as("JPG format should be supported") + .isTrue(); + assertThat(ImageFormatRegistry.isFormatSupported("jpeg")) + .as("JPEG format should be supported") + .isTrue(); } @Test public void testUnsupportedFormat() { - assertFalse(ImageFormatRegistry.isFormatSupported("xyz"), - "XYZ format should not be supported"); + assertThat(ImageFormatRegistry.isFormatSupported("xyz")) + .as("XYZ format should not be supported") + .isFalse(); } @Test @@ -45,42 +50,52 @@ public void testCreateInputFormats() { ImageFigure prototype = new ImageFigure(); List formats = ImageFormatRegistry.createInputFormats(prototype); - assertNotNull(formats, "Input formats should not be null"); - assertTrue(formats.size() >= 2, - "Should have at least 2 input formats (PNG, JPEG)"); + assertThat(formats) + .as("Input formats should not be null") + .isNotNull(); + assertThat(formats) + .as("Should have at least 2 input formats (PNG, JPEG)") + .hasSizeGreaterThanOrEqualTo(2); } @Test public void testCreateOutputFormats() { List formats = ImageFormatRegistry.createOutputFormats(); - assertNotNull(formats, "Output formats should not be null"); - assertTrue(formats.size() >= 2, - "Should have at least 2 output formats (PNG, JPEG)"); + assertThat(formats) + .as("Output formats should not be null") + .isNotNull(); + assertThat(formats) + .as("Should have at least 2 output formats (PNG, JPEG)") + .hasSizeGreaterThanOrEqualTo(2); } @Test public void testProviderCount() { List providers = ImageFormatRegistry.getProviders(); - assertNotNull(providers, "Providers should not be null"); - assertTrue(providers.size() >= 2, "Should have at least 2 providers"); + assertThat(providers) + .as("Providers should not be null") + .isNotNull(); + assertThat(providers) + .as("Should have at least 2 providers") + .hasSizeGreaterThanOrEqualTo(2); } @Test public void testPngProvider() { PngFormatProvider provider = new PngFormatProvider(); - assertEquals(provider.getFormatName(), "PNG"); - assertEquals(provider.getFileExtensions(), new String[]{"png"}); - assertEquals(provider.getMimeTypes(), new String[]{"image/png"}); + assertThat(provider.getFormatName()).isEqualTo("PNG"); + assertThat(provider.getFileExtensions()).isEqualTo(new String[]{"png"}); + assertThat(provider.getMimeTypes()).isEqualTo(new String[]{"image/png"}); } @Test public void testJpegProvider() { JpegFormatProvider provider = new JpegFormatProvider(); - assertEquals(provider.getFormatName(), "JPEG"); - assertEquals(provider.getFileExtensions(), new String[]{"jpg", "jpeg"}); + assertThat(provider.getFormatName()).isEqualTo("JPEG"); + assertThat(provider.getFileExtensions()).isEqualTo(new String[]{"jpg", "jpeg"}); } } From 5fce5fe01b0ec235f7d2503b7f69afaa64edff60 Mon Sep 17 00:00:00 2001 From: tjens23 Date: Sun, 4 Jan 2026 17:21:17 +0100 Subject: [PATCH 2/4] Fix CI: JGiven/ByteBuddy Java 24 compatibility and test fixes - Add maven-surefire-plugin with --add-opens JVM args for Java 17+ module system - Fix JGiven ambiguous field resolution using ScenarioState NAME resolution - Fix ViewManagerTest mock setup for testAddSameViewTwiceDoesNothing --- .../app/internal/ViewManagerTest.java | 2 +- .../draw/figure/bdd/GivenFigureCreation.java | 15 +++++-------- .../draw/figure/bdd/ThenFigureBehavior.java | 11 +++++----- .../figure/bdd/WhenFigureManipulation.java | 21 +++++++------------ pom.xml | 12 +++++++++++ 5 files changed, 32 insertions(+), 29 deletions(-) diff --git a/jhotdraw-app/src/test/java/org/jhotdraw/app/internal/ViewManagerTest.java b/jhotdraw-app/src/test/java/org/jhotdraw/app/internal/ViewManagerTest.java index a30d86b69..70cde7e9b 100644 --- a/jhotdraw-app/src/test/java/org/jhotdraw/app/internal/ViewManagerTest.java +++ b/jhotdraw-app/src/test/java/org/jhotdraw/app/internal/ViewManagerTest.java @@ -182,7 +182,7 @@ public void testSetActiveViewToNull() { */ @Test public void testAddSameViewTwiceDoesNothing() { - when(mockView1.getApplication()).thenReturn(mockApplication); + when(mockView1.getApplication()).thenReturn(null).thenReturn(mockApplication); viewManager.add(mockView1); viewManager.add(mockView1); // Second add should do nothing diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/GivenFigureCreation.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/GivenFigureCreation.java index eda646833..e75719eca 100644 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/GivenFigureCreation.java +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/GivenFigureCreation.java @@ -19,27 +19,22 @@ package org.jhotdraw.draw.figure.bdd; import com.tngtech.jgiven.Stage; -import com.tngtech.jgiven.annotation.ExpectedScenarioState; -import com.tngtech.jgiven.annotation.ProvidedScenarioState; +import com.tngtech.jgiven.annotation.ScenarioState; +import com.tngtech.jgiven.annotation.ScenarioState.Resolution; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import org.jhotdraw.draw.figure.RectangleFigure; import static org.assertj.core.api.Assertions.*; -/** - * JGiven Given stage for BDD testing of figure creation and setup scenarios. - * - * @author JHotDraw Team - */ public class GivenFigureCreation extends Stage { - @ProvidedScenarioState + @ScenarioState protected RectangleFigure rectangle; - @ProvidedScenarioState + @ScenarioState(resolution = Resolution.NAME) protected Point2D.Double startPoint; - @ProvidedScenarioState + @ScenarioState(resolution = Resolution.NAME) protected Point2D.Double endPoint; public GivenFigureCreation a_new_rectangle_figure() { diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/ThenFigureBehavior.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/ThenFigureBehavior.java index b9e021f89..f578300ef 100644 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/ThenFigureBehavior.java +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/ThenFigureBehavior.java @@ -19,7 +19,8 @@ package org.jhotdraw.draw.figure.bdd; import com.tngtech.jgiven.Stage; -import com.tngtech.jgiven.annotation.ExpectedScenarioState; +import com.tngtech.jgiven.annotation.ScenarioState; +import com.tngtech.jgiven.annotation.ScenarioState.Resolution; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import org.jhotdraw.draw.figure.RectangleFigure; @@ -27,16 +28,16 @@ public class ThenFigureBehavior extends Stage { - @ExpectedScenarioState + @ScenarioState protected RectangleFigure rectangle; - @ExpectedScenarioState + @ScenarioState protected Rectangle2D.Double resultingBounds; - @ExpectedScenarioState + @ScenarioState protected boolean containmentResult; - @ExpectedScenarioState + @ScenarioState(resolution = Resolution.NAME) protected Point2D.Double testPoint; public ThenFigureBehavior the_rectangle_should_have_position(double x, double y) { diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/WhenFigureManipulation.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/WhenFigureManipulation.java index ec90f3f68..9160d95ae 100644 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/WhenFigureManipulation.java +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/WhenFigureManipulation.java @@ -19,36 +19,31 @@ package org.jhotdraw.draw.figure.bdd; import com.tngtech.jgiven.Stage; -import com.tngtech.jgiven.annotation.ExpectedScenarioState; -import com.tngtech.jgiven.annotation.ProvidedScenarioState; +import com.tngtech.jgiven.annotation.ScenarioState; +import com.tngtech.jgiven.annotation.ScenarioState.Resolution; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import org.jhotdraw.draw.figure.RectangleFigure; -/** - * JGiven When stage for BDD testing of figure manipulation actions. - * - * @author JHotDraw Team - */ public class WhenFigureManipulation extends Stage { - @ExpectedScenarioState + @ScenarioState protected RectangleFigure rectangle; - @ExpectedScenarioState + @ScenarioState(resolution = Resolution.NAME) protected Point2D.Double startPoint; - @ExpectedScenarioState + @ScenarioState(resolution = Resolution.NAME) protected Point2D.Double endPoint; - @ProvidedScenarioState + @ScenarioState protected Rectangle2D.Double resultingBounds; - @ProvidedScenarioState + @ScenarioState protected boolean containmentResult; - @ProvidedScenarioState + @ScenarioState(resolution = Resolution.NAME) protected Point2D.Double testPoint; public WhenFigureManipulation the_user_sets_bounds_from_start_to_end_point() { diff --git a/pom.xml b/pom.xml index 5f6c7eef5..b7ce44319 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,18 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + --add-opens java.base/java.lang=ALL-UNNAMED + --add-opens java.base/java.lang.reflect=ALL-UNNAMED + --add-opens java.base/java.util=ALL-UNNAMED + + + From cfa873d34770f98e8dd8a5df0f5dcd7413e507cd Mon Sep 17 00:00:00 2001 From: tjens23 Date: Sun, 4 Jan 2026 17:34:16 +0100 Subject: [PATCH 3/4] =?UTF-8?q?Fix:=20SonarCloud=20Code=20Analysis=20Sonar?= =?UTF-8?q?Cloud=20Code=20AnalysisFailing=20after=2048s=20=E2=80=94=20Qual?= =?UTF-8?q?ity=20Gate=20failed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/internal/RecentFilesManager.java | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/jhotdraw-app/src/main/java/org/jhotdraw/app/internal/RecentFilesManager.java b/jhotdraw-app/src/main/java/org/jhotdraw/app/internal/RecentFilesManager.java index b6ba1c430..c29a06a9a 100644 --- a/jhotdraw-app/src/main/java/org/jhotdraw/app/internal/RecentFilesManager.java +++ b/jhotdraw-app/src/main/java/org/jhotdraw/app/internal/RecentFilesManager.java @@ -18,8 +18,9 @@ public class RecentFilesManager { private static final int MAX_RECENT_FILES_COUNT = 10; private final LinkedList recentURIs = new LinkedList<>(); private final Preferences preferences; - - public RecentFilesManager(Preferences preferences) { + private static final int RECENT_FILE_COUNT = 0; + private static final String RECENT_FILES_KEY = "recentFile."; + public RecentFilesManager(Preferences preferences) throws URISyntaxException { this.preferences = preferences; loadRecentFiles(); } @@ -27,16 +28,12 @@ public RecentFilesManager(Preferences preferences) { /** * Loads recent files from preferences using Template Method pattern. */ - private void loadRecentFiles() { - int count = preferences.getInt("recentFileCount", 0); + private void loadRecentFiles() throws URISyntaxException { + int count = preferences.getInt(String.valueOf(RECENT_FILE_COUNT), 0); for (int i = 0; i < count; i++) { - String path = preferences.get("recentFile." + i, null); + String path = preferences.get(RECENT_FILES_KEY + i, null); if (path != null) { - try { addRecentURIInternal(new URI(path)); - } catch (URISyntaxException ex) { - ex.printStackTrace(); - } } } } @@ -45,9 +42,9 @@ private void loadRecentFiles() { * Saves recent files to preferences. */ public void saveRecentFiles() { - preferences.putInt("recentFileCount", recentURIs.size()); + preferences.putInt(String.valueOf(RECENT_FILE_COUNT), recentURIs.size()); for (int i = 0; i < recentURIs.size(); i++) { - preferences.put("recentFile." + i, recentURIs.get(i).toString()); + preferences.put(RECENT_FILES_KEY + i, recentURIs.get(i).toString()); } } @@ -96,10 +93,10 @@ public List getRecentURIs() { */ public void clearRecentFiles() { recentURIs.clear(); - preferences.putInt("recentFileCount", 0); + preferences.putInt(String.valueOf(RECENT_FILE_COUNT), 0); // Remove all stored recent file entries for (int i = 0; i < MAX_RECENT_FILES_COUNT; i++) { - preferences.remove("recentFile." + i); + preferences.remove(RECENT_FILES_KEY + i); } } From 332fd1e23b8e1c1e69fb444f5a1bf5dd9a3415b1 Mon Sep 17 00:00:00 2001 From: tjens23 Date: Sun, 4 Jan 2026 17:57:22 +0100 Subject: [PATCH 4/4] Fix tests to match user story: Open PNG/JPEG image formats User Story: As a user, I want to be able to open various formats (PNG, JPEG) so I can work on top of my previous work TestLab1 - Unit Tests: - ImageFormatRegistryTest with JUnit 4, Mockito mocks, AssertJ assertions - Tests for PNG/JPEG format support, boundary cases, Java invariant assertions TestLab2 - BDD Tests: - ImageFormatBDDTest with JGiven Given-When-Then scenarios - 8 BDD scenarios covering opening PNG/JPEG, format validation, input/output formats Removed: - RectangleFigureTest (wrong feature) - RectangleFigureBDDTest (wrong feature) - AbstractFigureTest (wrong feature) --- ChangeReqLab_merged.txt | 224 ++++++++++++ .../draw/figure/AbstractFigureTest.java | 222 ------------ .../draw/figure/RectangleFigureTest.java | 341 ------------------ .../draw/figure/bdd/GivenFigureCreation.java | 69 ---- .../figure/bdd/RectangleFigureBDDTest.java | 124 ------- .../draw/figure/bdd/ThenFigureBehavior.java | 113 ------ .../figure/bdd/WhenFigureManipulation.java | 89 ----- .../draw/io/ImageFormatRegistryTest.java | 209 ++++++++++- .../draw/io/bdd/GivenImageFormat.java | 49 +++ .../draw/io/bdd/ImageFormatBDDTest.java | 98 +++++ .../draw/io/bdd/ThenImageFormatResult.java | 77 ++++ .../draw/io/bdd/WhenOpeningImage.java | 51 +++ 12 files changed, 692 insertions(+), 974 deletions(-) create mode 100644 ChangeReqLab_merged.txt delete mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/AbstractFigureTest.java delete mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/RectangleFigureTest.java delete mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/GivenFigureCreation.java delete mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/RectangleFigureBDDTest.java delete mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/ThenFigureBehavior.java delete mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/WhenFigureManipulation.java create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/io/bdd/GivenImageFormat.java create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/io/bdd/ImageFormatBDDTest.java create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/io/bdd/ThenImageFormatResult.java create mode 100644 jhotdraw-core/src/test/java/org/jhotdraw/draw/io/bdd/WhenOpeningImage.java diff --git a/ChangeReqLab_merged.txt b/ChangeReqLab_merged.txt new file mode 100644 index 000000000..279444bbf --- /dev/null +++ b/ChangeReqLab_merged.txt @@ -0,0 +1,224 @@ +[ChangeReqLab] +CASE Study Lab + +Introduction: Software changes are started by creating a change request. A change request can +be a new feature, a bug fix and improvements. +User stories are short and simple descriptions of features written from the perspective of the +person who desires the new capability. +Objectives: +• To write a user story for an existing software feauture. +• Select a user story for your mandatory individual portfolio assignment. +Classwork: +• Each Team creates a GitHub Fork of the project repository [JHotDraw]. +• In future lab exercises, each team member follow [GitHub flow] to collaborate on projects. +• Select an existing feature in JHotDraw and write the user story. See this list of [existing +features] +• Use [GitHub Projects] +• Create a Card for your User Story and put it in the TODO Backlog. +Portfolio Work: +• Write the User Story for your selected JHotDraw feature. Note this is a an artifact for your +portfolio. + +1 + + [CLLab] +Concept Location Lab + +Introduction: Dynamic program analysis is the analysis of computer software that is performed +by executing programs on a real or virtual processor. For dynamic program analysis to be effective, +the target program must be executed with sufficient test inputs to produce interesting behavior. +Use of software testing measures such as code coverage helps ensure that an adequate slice of the +program’s set of possible behaviors has been observed +Objectives: +• Apply the IDE Debugger to locate feature concepts at runtime. +• Create a list of initial set of classes of the concept location results. +Classwork: +• Use the IDE Debugger to localize the classes that involves your Change Request. Features +are often started from the controller classes. Note, if your feature involves a large number of +classes then localize only the domain classes that are related to the to domain concepts. +Portfolio Work: +• Write the initial set of classes in table format as a result of your concept location based on +your selected feature. The table format is as below: +Domain Class + +Responsibility + +1 + + [AnalysisLab] +Impact Analysis Lab + +Introduction: Change impact analysis (IA) can be defined as ”identifying the potential consequences of a change, or estimating what needs to be modified to accomplish a change”. +Objectives: +• Apply static and dynamic analysis to find the estimated impacted set of classes based on +your Change request. +Classwork: +• Find The Estimated Impact Set of classes by following the activities illustrated in Figure 7.9 +in [Raj13]. + +Portfolio Work: Use Table 1 to list the packages and the number of classes you visited after you +located the concept. Write short comments explaining what you have learned about each package +and how they contribute to your feature? + +1 + + Package name + +# of classes + +Comments + +Table 1: The list of all the packages visited during impact analysis. + +2 + + [CILab] +Impact Continues Integration Lab + +Introduction: In software engineering, continuous integration (CI) is the practice of merging +all developers’ working copies to a shared mainline several times a day [Tho]. Grady Booch first +proposed the term CI in his 1991 method although he did not advocate integrating several times +a day [Boo]. Extreme programming (XP) adopted the concept of CI and did advocate integrating +more than once per day – perhaps as many as tens of times per day [Bec99]. +Objectives: +• Understand what CI is. +• Setup a simple CI pipeline. +Classwork: +1. Go to [Building and testing Java with Maven] +2. Add a *.yml file to your repository path /.github/workflows/ to tell +GitHub Actions CI what to do. +3. Configure the *.yml to automatically build your project for each pull request (use maven). +4. To use shared jars from GitHub Packages you need create a .maven-settings.xml file in +the project root folder, see [Working with the Apache Maven registry] +5. Configure the *.yml to execute tests automatically. + +References +[Bec99] Kent Beck. Embracing change with extreme programming, 1999. [HTML]. +[Boo] + +Grady Booch. Object Oriented Design: With Applications. [HTML]. + +[Tho] + +ThoughtWorks. Continues Integration. [HTML]. + +1 + + [RefactLab] +Refactoring Lab +Jan Corfixen Sørensen +Introduction: Refactoring is a disciplined technique for restructuring an existing body of code, +altering its internal structure without changing its external behavior. Its heart is a series of small +behavior preserving transformations. Each transformation (called a ”refactoring”) does little, but a +sequence of these transformations can produce a significant restructuring. Since each refactoring is +small, it’s less likely to go wrong. The system is kept fully working after each refactoring, reducing +the chances that a system can get seriously broken during the restructuring. +Objectives: +• Identify and understand Bad Code Smells. +• Apply refactorings to get rid of Bad Code. +Classwork: +• Make sure you have your own feature branch, using “git checkout -b your-feature development”. +• Please follow the feature branch workflow [GitHub flow]. +• Install [sonarlint]. +• Find Code smells in JHotDraw based on your change request and sonarlint (shouldn’t be a +problem). +• Apply one or more suitable Refactoring Patterns to get rid of the bad code smells 1 . +Portfolio Work: +• Describe the code smell that triggered your refactoring, see Chapter 4 in [Ker05]. Describe +what you plan to change by refactoring. Describe the strategy of the refactorings. Which of +the refactorings from [Ker05] did you apply and what was the reasoning behind it? +• Remember to describe the strategies and purpose of the Refactorings. + +1 See http://refactoring.com/catalog/ + +1 + + [ActLab] +Actualization Lab +Jan Corfixen Sørensen +Introduction: The actualization phase consists of the implementation of the new functionality, +its incorporation into the old code, and change propagation that seeks out and updates all places +in the old code that require secondary modification. +Objectives: +• Understand and explain Clean Architecture in context of Actualization +• Understand and explain Clean Code Principles in context of Actualization +Portfolio Work: +• Provide examples of the SOLID principles in context of the CASE study. +• Explain Clean Architecture in context of the CASE Study. +• + +1 + + [ActLab] +Actualization Lab +Jan Corfixen Sørensen +Introduction: The actualization phase consists of the implementation of the new functionality, +its incorporation into the old code, and change propagation that seeks out and updates all places +in the old code that require secondary modification. +Objectives: +• Understand and explain Clean Architecture in context of Actualization +• Understand and explain Clean Code Principles in context of Actualization +Portfolio Work: +• Provide examples of the SOLID principles in context of the CASE study. +• Explain Clean Architecture in context of the CASE Study. +• + +1 + + [TestLab1] +Testing + +Introduction: In computer programming, unit testing is a software testing method by which +individual units of source code—sets of one or more computer program modules together with +associated control data, usage procedures, and operating procedures—are tested to determine +whether they are fit for use. Unit tests are typically automated tests written and run by software +developers to ensure that a section of an application (known as the ”unit”) meets its design and +behaves as intended. In procedural programming, a unit could be an entire module, but it is +more commonly an individual function or procedure. In object-oriented programming, a unit is +often an entire interface, such as a class, but could be an individual method. By writing tests +first for the smallest testable units, then the compound behaviors between those, one can build up +comprehensive tests for complex applications. +Objectives: +• Understand the importance of testing. +• Implement unit tests. +Classwork: +1. Add maven dependency to [JUnit4] if it is not already done. +2. Create JUnit 4 tests for most important domain logic methods of your feature. Note: Swing +and JUnit extensions often works best with JUnit 4. +3. Write JUnit tests for best case scenario +4. Write JUnit tests for identified boundary cases +(a) A unit test should test a single code-path through a single method. When the execution +of a method passes outside of that method, you have a dependency and should apply +mocks/stubs to avoid the dependency, see [mockito.org]. +5. Use JAVA Assertions to test invariants, see [JAVA Asserts]. +(a) Assertions should be used to check something that should never happen +(b) Note, an assertion should stop the program from running, but an exception should let +the program continue running. +Portfolio Work: +• At class level write unit tests of important business functionality of your selected Feature. +Document how you have verified your Feature. + +1 + + [TestLab2] +Behavior Driven Testing + +Introduction – User Stories and BBD +User stories are short and simple descriptions of capabilities written from the perspective of the +person who desires the new capability. “As a [user type], I want [some goal] so that +[some reason].” An alternative includes: “As a [user type], I want [some goal] + +because [why].” +Mapping User Stories to BDD: + +Figure 1: Example of how to map User Story to BDD scenario. + +Portfolio: +• Map your User Stories to BDD Given-When-Then Scenarios +• Use [JGiven] to automate your BDD Scenarios +• For domain specific assertions use the [AssertJ library]. +For Swing applications use the [AssertJ-swing] to automate the Scenarios. + +1 + + \ No newline at end of file diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/AbstractFigureTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/AbstractFigureTest.java deleted file mode 100644 index 605d7f92b..000000000 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/AbstractFigureTest.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2015 JHotDraw. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ -package org.jhotdraw.draw.figure; - -import java.awt.Graphics2D; -import java.awt.geom.AffineTransform; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.util.Map; -import org.jhotdraw.draw.AttributeKey; -import org.junit.*; -import static org.assertj.core.api.Assertions.*; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Unit tests for AbstractFigure using JUnit 4 and AssertJ. - * Tests focus on core figure behavior, attribute management, and geometric operations. - * - * @author tw - */ -public class AbstractFigureTest { - - @Mock - private Graphics2D mockGraphics; - - private TestFigure figure; - - @BeforeClass - public static void setUpClass() throws Exception { - } - - @AfterClass - public static void tearDownClass() throws Exception { - } - - @Before - public void setUp() throws Exception { - MockitoAnnotations.openMocks(this); - figure = new TestFigure(); - } - - @After - public void tearDown() throws Exception { - } - - /** - * Test that calling changed() without willChange() throws exception. - * This is a crucial invariant that should never be violated. - */ - @Test(expected = IllegalStateException.class) - public void testChangedWithoutWillChange() { - new TestFigure().changed(); - } - - /** - * Test the willChange/changed event depth tracking. - * Validates proper nesting of change events. - */ - @Test - public void testWillChangeChangedEvents() { - TestFigure testFigure = new TestFigure(); - - // Initially no changes are pending - assertThat(testFigure.getChangingDepth()).isZero(); - - // First willChange call - testFigure.willChange(); - assertThat(testFigure.getChangingDepth()).isEqualTo(1); - - // Nested willChange call - testFigure.willChange(); - assertThat(testFigure.getChangingDepth()).isEqualTo(2); - - // First changed call - testFigure.changed(); - assertThat(testFigure.getChangingDepth()).isEqualTo(1); - - // Final changed call - testFigure.changed(); - assertThat(testFigure.getChangingDepth()).isZero(); - } - - /** - * Test boundary condition: multiple nested willChange calls. - */ - @Test - public void testDeepNestedWillChangeEvents() { - TestFigure testFigure = new TestFigure(); - - // Create deep nesting - for (int i = 1; i <= 10; i++) { - testFigure.willChange(); - assertThat(testFigure.getChangingDepth()).isEqualTo(i); - } - - // Unwind the nesting - for (int i = 9; i >= 0; i--) { - testFigure.changed(); - assertThat(testFigure.getChangingDepth()).isEqualTo(i); - } - } - - /** - * Test figure drawing behavior with mock graphics context. - */ - @Test - public void testDrawWithMockGraphics() { - // This test demonstrates mocking for isolating drawing behavior - figure.draw(mockGraphics); - // In a real implementation, we would verify specific drawing calls - // For now, just ensure no exceptions are thrown - assertThat(figure).isNotNull(); - } - - /** - * Test figure bounds calculation - edge case with null bounds. - */ - @Test - public void testGetBoundsReturnsNull() { - Rectangle2D.Double bounds = figure.getBounds(); - assertThat(bounds).isNull(); // Current implementation returns null - } - - /** - * Test figure containment - current implementation always returns true. - */ - @Test - public void testContainsPoint() { - Point2D.Double point = new Point2D.Double(10, 10); - boolean contains = figure.contains(point); - assertThat(contains).isTrue(); // Current implementation - } - - /** - * Test helper class that extends AbstractFigure for testing purposes. - * This class provides minimal implementations needed for testing. - */ - private static class TestFigure extends AbstractFigure { - - @Override - public void draw(Graphics2D g) { - // Minimal implementation for testing - } - - @Override - public Rectangle2D.Double getBounds() { - return null; // Simplified for testing - } - - @Override - public Rectangle2D.Double getDrawingArea() { - return null; // Simplified for testing - } - - @Override - public boolean contains(Point2D.Double p) { - return true; // Simplified for testing - } - - @Override - public Object getTransformRestoreData() { - return null; // Simplified for testing - } - - @Override - public void restoreTransformTo(Object restoreData) { - // Minimal implementation for testing - } - - @Override - public void transform(AffineTransform tx) { - // Minimal implementation for testing - } - - @Override - public void set(AttributeKey key, T value) { - // Minimal implementation for testing - } - - @Override - public T get(AttributeKey key) { - return null; // Simplified for testing - } - - @Override - public Map, Object> getAttributes() { - return java.util.Collections.emptyMap(); // Simplified for testing - } - - @Override - public Object getAttributesRestoreData() { - return null; // Simplified for testing - } - - @Override - public void restoreAttributesTo(Object restoreData) { - // Minimal implementation for testing - } - - @Override - public Rectangle2D.Double getDrawingArea(double factor) { - return null; // Simplified for testing - } - } -} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/RectangleFigureTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/RectangleFigureTest.java deleted file mode 100644 index 1e2175e37..000000000 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/RectangleFigureTest.java +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright (C) 2015 JHotDraw. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ -package org.jhotdraw.draw.figure; - -import java.awt.Graphics2D; -import java.awt.geom.AffineTransform; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.awt.image.BufferedImage; -import org.junit.*; -import static org.assertj.core.api.Assertions.*; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import static org.mockito.Mockito.*; - -/** - * Comprehensive unit tests for RectangleFigure using JUnit 4, AssertJ, and Mockito. - * Tests focus on geometric operations, boundary conditions, and drawing behavior. - * - * @author JHotDraw Team - */ -public class RectangleFigureTest { - - @Mock - private Graphics2D mockGraphics; - - private RectangleFigure rectangle; - - @BeforeClass - public static void setUpClass() { - // Class-level setup if needed - } - - @AfterClass - public static void tearDownClass() { - // Class-level cleanup if needed - } - - @Before - public void setUp() { - MockitoAnnotations.openMocks(this); - rectangle = new RectangleFigure(); - } - - @After - public void tearDown() { - rectangle = null; - } - - // CONSTRUCTOR TESTS - - /** - * Test default constructor creates rectangle at origin with zero dimensions. - */ - @Test - public void testDefaultConstructor() { - RectangleFigure rect = new RectangleFigure(); - Rectangle2D.Double bounds = rect.getBounds(); - - assertThat(bounds.x).isZero(); - assertThat(bounds.y).isZero(); - assertThat(bounds.width).isZero(); - assertThat(bounds.height).isZero(); - } - - /** - * Test parameterized constructor sets dimensions correctly. - */ - @Test - public void testParameterizedConstructor() { - RectangleFigure rect = new RectangleFigure(10, 20, 100, 50); - Rectangle2D.Double bounds = rect.getBounds(); - - assertThat(bounds.x).isEqualTo(10.0); - assertThat(bounds.y).isEqualTo(20.0); - assertThat(bounds.width).isEqualTo(100.0); - assertThat(bounds.height).isEqualTo(50.0); - } - - // BOUNDARY TESTS - - /** - * Test setBounds with normal positive values. - */ - @Test - public void testSetBounds() { - Point2D.Double anchor = new Point2D.Double(10, 10); - Point2D.Double lead = new Point2D.Double(50, 30); - - rectangle.setBounds(anchor, lead); - Rectangle2D.Double bounds = rectangle.getBounds(); - - assertThat(bounds.x).isEqualTo(10.0); - assertThat(bounds.y).isEqualTo(10.0); - assertThat(bounds.width).isEqualTo(40.0); - assertThat(bounds.height).isEqualTo(20.0); - } - - /** - * Test setBounds with inverted anchor and lead points (lead before anchor). - */ - @Test - public void testSetBoundsInvertedPoints() { - Point2D.Double anchor = new Point2D.Double(50, 30); - Point2D.Double lead = new Point2D.Double(10, 10); - - rectangle.setBounds(anchor, lead); - Rectangle2D.Double bounds = rectangle.getBounds(); - - // Should normalize to proper rectangle - assertThat(bounds.x).isEqualTo(10.0); - assertThat(bounds.y).isEqualTo(10.0); - assertThat(bounds.width).isEqualTo(40.0); - assertThat(bounds.height).isEqualTo(20.0); - } - - /** - * Test setBounds with zero-width rectangle enforces minimum width. - */ - @Test - public void testSetBoundsZeroWidth() { - Point2D.Double anchor = new Point2D.Double(10, 10); - Point2D.Double lead = new Point2D.Double(10, 30); // Same x-coordinate - - rectangle.setBounds(anchor, lead); - Rectangle2D.Double bounds = rectangle.getBounds(); - - assertThat(bounds.width).isEqualTo(0.1); // Minimum width enforced - assertThat(bounds.height).isEqualTo(20.0); - } - - /** - * Test setBounds with zero-height rectangle enforces minimum height. - */ - @Test - public void testSetBoundsZeroHeight() { - Point2D.Double anchor = new Point2D.Double(10, 10); - Point2D.Double lead = new Point2D.Double(30, 10); // Same y-coordinate - - rectangle.setBounds(anchor, lead); - Rectangle2D.Double bounds = rectangle.getBounds(); - - assertThat(bounds.width).isEqualTo(20.0); - assertThat(bounds.height).isEqualTo(0.1); // Minimum height enforced - } - - // CONTAINMENT TESTS - - /** - * Test contains method for point inside rectangle. - */ - @Test - public void testContainsPointInside() { - RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); - Point2D.Double insidePoint = new Point2D.Double(20, 17); - - assertThat(rect.contains(insidePoint)).isTrue(); - } - - /** - * Test contains method for point outside rectangle. - */ - @Test - public void testContainsPointOutside() { - RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); - Point2D.Double outsidePoint = new Point2D.Double(5, 5); - - assertThat(rect.contains(outsidePoint)).isFalse(); - } - - /** - * Test contains method for point on rectangle boundary. - */ - @Test - public void testContainsPointOnBoundary() { - RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); - Point2D.Double boundaryPoint = new Point2D.Double(10, 10); // Top-left corner - - // Due to hit growth, boundary points should be contained - assertThat(rect.contains(boundaryPoint)).isTrue(); - } - - // TRANSFORMATION TESTS - - /** - * Test transform with identity transformation. - */ - @Test - public void testTransformIdentity() { - RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); - Rectangle2D.Double originalBounds = rect.getBounds(); - - AffineTransform identity = new AffineTransform(); - rect.transform(identity); - - Rectangle2D.Double newBounds = rect.getBounds(); - assertThat(newBounds).isEqualTo(originalBounds); - } - - /** - * Test transform with translation. - */ - @Test - public void testTransformTranslation() { - RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); - - AffineTransform translation = AffineTransform.getTranslateInstance(5, -3); - rect.transform(translation); - - Rectangle2D.Double bounds = rect.getBounds(); - assertThat(bounds.x).isEqualTo(15.0); - assertThat(bounds.y).isEqualTo(7.0); - assertThat(bounds.width).isEqualTo(20.0); - assertThat(bounds.height).isEqualTo(15.0); - } - - /** - * Test transform with scaling. - */ - @Test - public void testTransformScaling() { - RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); - - AffineTransform scaling = AffineTransform.getScaleInstance(2.0, 0.5); - rect.transform(scaling); - - Rectangle2D.Double bounds = rect.getBounds(); - assertThat(bounds.x).isEqualTo(20.0); - assertThat(bounds.y).isEqualTo(5.0); - assertThat(bounds.width).isEqualTo(40.0); - assertThat(bounds.height).isEqualTo(7.5); - } - - // DRAWING AREA TESTS - - /** - * Test that drawing area is larger than bounds due to stroke growth. - */ - @Test - public void testGetDrawingAreaLargerThanBounds() { - RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); - - Rectangle2D.Double bounds = rect.getBounds(); - Rectangle2D.Double drawingArea = rect.getDrawingArea(); - - assertThat(drawingArea.width).isGreaterThan(bounds.width); - assertThat(drawingArea.height).isGreaterThan(bounds.height); - } - - // DRAWING BEHAVIOR TESTS (with mocking) - - /** - * Test that draw method calls appropriate Graphics2D methods. - * Note: This is a simplified test - in reality we'd need to set up proper attributes. - */ - @Test - public void testDrawCallsGraphicsMethods() { - RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); - - // Create a real graphics context for testing - BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); - Graphics2D g2d = image.createGraphics(); - - // This should not throw any exceptions - assertThatCode(() -> rect.draw(g2d)).doesNotThrowAnyException(); - - g2d.dispose(); - } - - // EDGE CASES AND ERROR CONDITIONS - - /** - * Test drawing with null graphics context should throw NPE. - */ - @Test - public void testDrawWithNullGraphics() { - RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); - - // Should throw NullPointerException - this is the correct behavior - assertThatThrownBy(() -> rect.draw(null)) - .isInstanceOf(NullPointerException.class); - } - - /** - * Test negative dimensions behavior. - */ - @Test - public void testNegativeDimensions() { - RectangleFigure rect = new RectangleFigure(10, 10, -20, -15); - Rectangle2D.Double bounds = rect.getBounds(); - - // Should handle negative dimensions appropriately - assertThat(bounds.width).isEqualTo(-20.0); // Current behavior - might need adjustment - assertThat(bounds.height).isEqualTo(-15.0); - } - - // JAVA ASSERTIONS FOR INVARIANTS - - /** - * Test invariant: bounds should never be null. - */ - @Test - public void testBoundsInvariant() { - assert rectangle.getBounds() != null : "Bounds should never be null"; - - rectangle.setBounds(new Point2D.Double(0, 0), new Point2D.Double(10, 10)); - assert rectangle.getBounds() != null : "Bounds should never be null after setBounds"; - - rectangle.transform(AffineTransform.getScaleInstance(2, 2)); - assert rectangle.getBounds() != null : "Bounds should never be null after transform"; - } - - /** - * Test invariant: drawing area should always contain bounds. - */ - @Test - public void testDrawingAreaContainsBoundsInvariant() { - RectangleFigure rect = new RectangleFigure(10, 10, 20, 15); - - Rectangle2D.Double bounds = rect.getBounds(); - Rectangle2D.Double drawingArea = rect.getDrawingArea(); - - assert drawingArea.contains(bounds) : "Drawing area should always contain bounds"; - } -} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/GivenFigureCreation.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/GivenFigureCreation.java deleted file mode 100644 index e75719eca..000000000 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/GivenFigureCreation.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2015 JHotDraw. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ -package org.jhotdraw.draw.figure.bdd; - -import com.tngtech.jgiven.Stage; -import com.tngtech.jgiven.annotation.ScenarioState; -import com.tngtech.jgiven.annotation.ScenarioState.Resolution; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import org.jhotdraw.draw.figure.RectangleFigure; -import static org.assertj.core.api.Assertions.*; - -public class GivenFigureCreation extends Stage { - - @ScenarioState - protected RectangleFigure rectangle; - - @ScenarioState(resolution = Resolution.NAME) - protected Point2D.Double startPoint; - - @ScenarioState(resolution = Resolution.NAME) - protected Point2D.Double endPoint; - - public GivenFigureCreation a_new_rectangle_figure() { - rectangle = new RectangleFigure(); - return self(); - } - - public GivenFigureCreation a_rectangle_figure_with_dimensions(double x, double y, double width, double height) { - rectangle = new RectangleFigure(x, y, width, height); - return self(); - } - - public GivenFigureCreation an_existing_rectangle_at_position(double x, double y) { - rectangle = new RectangleFigure(x, y, 50, 30); // Default size - return self(); - } - - public GivenFigureCreation a_start_point_at(double x, double y) { - startPoint = new Point2D.Double(x, y); - return self(); - } - - public GivenFigureCreation an_end_point_at(double x, double y) { - endPoint = new Point2D.Double(x, y); - return self(); - } - - public GivenFigureCreation a_rectangle_with_zero_dimensions() { - rectangle = new RectangleFigure(10, 10, 0, 0); - return self(); - } -} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/RectangleFigureBDDTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/RectangleFigureBDDTest.java deleted file mode 100644 index f5d1f292a..000000000 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/RectangleFigureBDDTest.java +++ /dev/null @@ -1,124 +0,0 @@ -package org.jhotdraw.draw.figure.bdd; - -import com.tngtech.jgiven.junit.ScenarioTest; -import org.junit.Test; - -public class RectangleFigureBDDTest extends ScenarioTest { - - @Test - public void user_can_create_rectangle_by_dragging() { - given().a_new_rectangle_figure() - .and().a_start_point_at(10, 10) - .and().an_end_point_at(50, 40); - - when().the_user_creates_a_rectangle_by_dragging_from_start_to_end(); - - then().the_rectangle_should_have_position(10, 10) - .and().the_rectangle_should_have_dimensions(40, 30); - } - - @Test - public void user_can_resize_existing_rectangle() { - given().a_rectangle_figure_with_dimensions(10, 10, 50, 30); - - when().the_user_resizes_the_rectangle_to(100, 60); - - then().the_rectangle_should_have_dimensions(100, 60) - .and().the_rectangle_should_be_positioned_at(10, 10); - } - - @Test - public void user_can_move_rectangle_to_new_position() { - given().a_rectangle_figure_with_dimensions(10, 10, 50, 30); - - when().the_user_moves_the_figure_by(20, 15); - - then().the_rectangle_should_have_position(30, 25) - .and().the_rectangle_should_have_dimensions(50, 30); - } - - @Test - public void user_can_scale_rectangle_proportionally() { - given().a_rectangle_figure_with_dimensions(10, 10, 40, 20); - - when().the_user_scales_the_figure_by(2.0, 2.0); - - then().the_rectangle_should_maintain_aspect_ratio_after_uniform_scaling(40, 20, 2.0); - } - - @Test - public void rectangle_enforces_minimum_dimensions_when_dragging() { - given().a_new_rectangle_figure() - .and().a_start_point_at(10, 10) - .and().an_end_point_at(10, 10); // Same point - zero dimensions - - when().the_user_creates_a_rectangle_by_dragging_from_start_to_end(); - - then().the_rectangle_should_enforce_minimum_width(0.1) - .and().the_rectangle_should_enforce_minimum_height(0.1); - } - - @Test - public void rectangle_normalizes_bounds_when_dragging_backwards() { - given().a_new_rectangle_figure() - .and().a_start_point_at(50, 40) // Start from bottom-right - .and().an_end_point_at(10, 10); // End at top-left - - when().the_user_creates_a_rectangle_by_dragging_from_start_to_end(); - - then().the_rectangle_should_have_position(10, 10) // Should normalize to top-left - .and().the_rectangle_should_have_dimensions(40, 30) - .and().the_rectangle_should_be_normalized(); - } - - @Test - public void point_inside_rectangle_is_detected_correctly() { - given().a_rectangle_figure_with_dimensions(10, 10, 40, 30); - - when().checking_if_point_$_$_is_contained(25, 20); // Point inside - - then().the_point_should_be_contained(); - } - - @Test - public void point_outside_rectangle_is_detected_correctly() { - given().a_rectangle_figure_with_dimensions(10, 10, 40, 30); - - when().checking_if_point_$_$_is_contained(5, 5); // Point outside - - then().the_point_should_not_be_contained(); - } - - @Test - public void rectangle_drawing_area_accommodates_stroke_width() { - given().a_rectangle_figure_with_dimensions(10, 10, 40, 30); - - when().the_user_moves_the_figure_by(0, 0); // Trigger bounds calculation - - then().the_drawing_area_should_be_larger_than_bounds(); - } - - @Test - public void user_can_create_thin_horizontal_line() { - given().a_new_rectangle_figure() - .and().a_start_point_at(10, 20) - .and().an_end_point_at(50, 20); // Same y-coordinate - - when().the_user_creates_a_rectangle_by_dragging_from_start_to_end(); - - then().the_rectangle_should_have_position(10, 20) - .and().the_rectangle_should_have_dimensions(40, 0.1); // Minimum height enforced - } - - @Test - public void user_can_create_thin_vertical_line() { - given().a_new_rectangle_figure() - .and().a_start_point_at(15, 10) - .and().an_end_point_at(15, 40); // Same x-coordinate - - when().the_user_creates_a_rectangle_by_dragging_from_start_to_end(); - - then().the_rectangle_should_have_position(15, 10) - .and().the_rectangle_should_have_dimensions(0.1, 30); // Minimum width enforced - } -} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/ThenFigureBehavior.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/ThenFigureBehavior.java deleted file mode 100644 index f578300ef..000000000 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/ThenFigureBehavior.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2015 JHotDraw. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ -package org.jhotdraw.draw.figure.bdd; - -import com.tngtech.jgiven.Stage; -import com.tngtech.jgiven.annotation.ScenarioState; -import com.tngtech.jgiven.annotation.ScenarioState.Resolution; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import org.jhotdraw.draw.figure.RectangleFigure; -import static org.assertj.core.api.Assertions.*; - -public class ThenFigureBehavior extends Stage { - - @ScenarioState - protected RectangleFigure rectangle; - - @ScenarioState - protected Rectangle2D.Double resultingBounds; - - @ScenarioState - protected boolean containmentResult; - - @ScenarioState(resolution = Resolution.NAME) - protected Point2D.Double testPoint; - - public ThenFigureBehavior the_rectangle_should_have_position(double x, double y) { - assertThat(resultingBounds.x).isEqualTo(x); - assertThat(resultingBounds.y).isEqualTo(y); - return self(); - } - - public ThenFigureBehavior the_rectangle_should_have_dimensions(double width, double height) { - assertThat(resultingBounds.width).isEqualTo(width); - assertThat(resultingBounds.height).isEqualTo(height); - return self(); - } - - public ThenFigureBehavior the_rectangle_should_be_positioned_at(double x, double y) { - Rectangle2D.Double bounds = rectangle.getBounds(); - assertThat(bounds.x).isEqualTo(x); - assertThat(bounds.y).isEqualTo(y); - return self(); - } - - public ThenFigureBehavior the_rectangle_should_have_size(double width, double height) { - Rectangle2D.Double bounds = rectangle.getBounds(); - assertThat(bounds.width).isEqualTo(width); - assertThat(bounds.height).isEqualTo(height); - return self(); - } - - public ThenFigureBehavior the_point_should_be_contained() { - assertThat(containmentResult).as("Point %s should be contained in rectangle", testPoint).isTrue(); - return self(); - } - - public ThenFigureBehavior the_point_should_not_be_contained() { - assertThat(containmentResult).as("Point %s should not be contained in rectangle", testPoint).isFalse(); - return self(); - } - - public ThenFigureBehavior the_rectangle_should_enforce_minimum_width(double minimumWidth) { - assertThat(resultingBounds.width).isGreaterThanOrEqualTo(minimumWidth); - return self(); - } - - public ThenFigureBehavior the_rectangle_should_enforce_minimum_height(double minimumHeight) { - assertThat(resultingBounds.height).isGreaterThanOrEqualTo(minimumHeight); - return self(); - } - - public ThenFigureBehavior the_rectangle_should_be_normalized() { - // A normalized rectangle should have positive width and height - assertThat(resultingBounds.width).isGreaterThanOrEqualTo(0); - assertThat(resultingBounds.height).isGreaterThanOrEqualTo(0); - return self(); - } - - public ThenFigureBehavior the_drawing_area_should_be_larger_than_bounds() { - Rectangle2D.Double bounds = rectangle.getBounds(); - Rectangle2D.Double drawingArea = rectangle.getDrawingArea(); - - assertThat(drawingArea.width).isGreaterThan(bounds.width); - assertThat(drawingArea.height).isGreaterThan(bounds.height); - return self(); - } - - public ThenFigureBehavior the_rectangle_should_maintain_aspect_ratio_after_uniform_scaling(double originalWidth, double originalHeight, double scaleFactor) { - double expectedWidth = originalWidth * scaleFactor; - double expectedHeight = originalHeight * scaleFactor; - - assertThat(resultingBounds.width).isCloseTo(expectedWidth, within(0.01)); - assertThat(resultingBounds.height).isCloseTo(expectedHeight, within(0.01)); - return self(); - } -} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/WhenFigureManipulation.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/WhenFigureManipulation.java deleted file mode 100644 index 9160d95ae..000000000 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/bdd/WhenFigureManipulation.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2015 JHotDraw. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ -package org.jhotdraw.draw.figure.bdd; - -import com.tngtech.jgiven.Stage; -import com.tngtech.jgiven.annotation.ScenarioState; -import com.tngtech.jgiven.annotation.ScenarioState.Resolution; -import java.awt.geom.AffineTransform; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import org.jhotdraw.draw.figure.RectangleFigure; - -public class WhenFigureManipulation extends Stage { - - @ScenarioState - protected RectangleFigure rectangle; - - @ScenarioState(resolution = Resolution.NAME) - protected Point2D.Double startPoint; - - @ScenarioState(resolution = Resolution.NAME) - protected Point2D.Double endPoint; - - @ScenarioState - protected Rectangle2D.Double resultingBounds; - - @ScenarioState - protected boolean containmentResult; - - @ScenarioState(resolution = Resolution.NAME) - protected Point2D.Double testPoint; - - public WhenFigureManipulation the_user_sets_bounds_from_start_to_end_point() { - rectangle.setBounds(startPoint, endPoint); - resultingBounds = rectangle.getBounds(); - return self(); - } - - public WhenFigureManipulation the_user_moves_the_figure_by(double deltaX, double deltaY) { - AffineTransform translation = AffineTransform.getTranslateInstance(deltaX, deltaY); - rectangle.transform(translation); - resultingBounds = rectangle.getBounds(); - return self(); - } - - public WhenFigureManipulation the_user_scales_the_figure_by(double scaleX, double scaleY) { - AffineTransform scaling = AffineTransform.getScaleInstance(scaleX, scaleY); - rectangle.transform(scaling); - resultingBounds = rectangle.getBounds(); - return self(); - } - - public WhenFigureManipulation checking_if_point_$_$_is_contained(double x, double y) { - testPoint = new Point2D.Double(x, y); - containmentResult = rectangle.contains(testPoint); - return self(); - } - - public WhenFigureManipulation the_user_resizes_the_rectangle_to(double width, double height) { - Rectangle2D.Double currentBounds = rectangle.getBounds(); - Point2D.Double anchor = new Point2D.Double(currentBounds.x, currentBounds.y); - Point2D.Double lead = new Point2D.Double(currentBounds.x + width, currentBounds.y + height); - rectangle.setBounds(anchor, lead); - resultingBounds = rectangle.getBounds(); - return self(); - } - - public WhenFigureManipulation the_user_creates_a_rectangle_by_dragging_from_start_to_end() { - rectangle.setBounds(startPoint, endPoint); - resultingBounds = rectangle.getBounds(); - return self(); - } -} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/ImageFormatRegistryTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/ImageFormatRegistryTest.java index 83da0cbf5..5611bc73c 100644 --- a/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/ImageFormatRegistryTest.java +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/ImageFormatRegistryTest.java @@ -8,16 +8,51 @@ package org.jhotdraw.draw.io; import org.jhotdraw.draw.figure.ImageFigure; -import org.junit.Test; +import org.jhotdraw.draw.figure.ImageHolderFigure; +import org.junit.*; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.util.List; import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; /** - * Tests for the modular ImageFormatRegistry using JUnit 4 and AssertJ. + * Unit tests for ImageFormatRegistry - supports the user story: + * "As a user, I want to be able to open various formats (PNG, JPEG) + * so I can work on top of my previous work" + * + * Tests use JUnit 4, Mockito for mocking, AssertJ for assertions, + * and Java assertions for invariants. */ public class ImageFormatRegistryTest { + @Mock + private ImageHolderFigure mockPrototype; + + @Mock + private ImageFormatProvider mockProvider; + + private AutoCloseable mockCloseable; + + @BeforeClass + public static void setUpClass() { + // Enable Java assertions for invariant testing + ImageFormatRegistryTest.class.getClassLoader().setDefaultAssertionStatus(true); + } + + @Before + public void setUp() { + mockCloseable = MockitoAnnotations.openMocks(this); + } + + @After + public void tearDown() throws Exception { + mockCloseable.close(); + } + + // ==================== BEST CASE SCENARIO TESTS ==================== + @Test public void testPngFormatSupported() { assertThat(ImageFormatRegistry.isFormatSupported("png")) @@ -36,17 +71,13 @@ public void testJpegFormatSupported() { assertThat(ImageFormatRegistry.isFormatSupported("jpeg")) .as("JPEG format should be supported") .isTrue(); + assertThat(ImageFormatRegistry.isFormatSupported("JPEG")) + .as("JPEG format should be supported (uppercase)") + .isTrue(); } @Test - public void testUnsupportedFormat() { - assertThat(ImageFormatRegistry.isFormatSupported("xyz")) - .as("XYZ format should not be supported") - .isFalse(); - } - - @Test - public void testCreateInputFormats() { + public void testCreateInputFormatsWithRealPrototype() { ImageFigure prototype = new ImageFigure(); List formats = ImageFormatRegistry.createInputFormats(prototype); @@ -70,6 +101,32 @@ public void testCreateOutputFormats() { .hasSizeGreaterThanOrEqualTo(2); } + // ==================== BOUNDARY CASE TESTS ==================== + + @Test + public void testUnsupportedFormat() { + assertThat(ImageFormatRegistry.isFormatSupported("xyz")) + .as("XYZ format should not be supported") + .isFalse(); + } + + @Test + public void testEmptyExtension() { + assertThat(ImageFormatRegistry.isFormatSupported("")) + .as("Empty extension should not be supported") + .isFalse(); + } + + @Test + public void testCaseInsensitiveFormatCheck() { + assertThat(ImageFormatRegistry.isFormatSupported("PnG")) + .as("Format check should be case-insensitive") + .isTrue(); + assertThat(ImageFormatRegistry.isFormatSupported("JpEg")) + .as("Format check should be case-insensitive") + .isTrue(); + } + @Test public void testProviderCount() { List providers = ImageFormatRegistry.getProviders(); @@ -78,24 +135,144 @@ public void testProviderCount() { .as("Providers should not be null") .isNotNull(); assertThat(providers) - .as("Should have at least 2 providers") + .as("Should have at least 2 providers (PNG, JPEG)") .hasSizeGreaterThanOrEqualTo(2); } + // ==================== MOCKITO MOCK TESTS ==================== + @Test - public void testPngProvider() { + public void testCreateInputFormatWithMockedPrototype() { + when(mockPrototype.clone()).thenReturn(mockPrototype); + + List formats = ImageFormatRegistry.createInputFormats(mockPrototype); + + assertThat(formats) + .as("Should create input formats with mocked prototype") + .isNotEmpty(); + } + + @Test + public void testMockedProviderBehavior() { + when(mockProvider.getFormatName()).thenReturn("TEST"); + when(mockProvider.getFileExtensions()).thenReturn(new String[]{"test"}); + when(mockProvider.getMimeTypes()).thenReturn(new String[]{"image/test"}); + when(mockProvider.getDescription()).thenReturn("Test Format"); + + assertThat(mockProvider.getFormatName()).isEqualTo("TEST"); + assertThat(mockProvider.getFileExtensions()).containsExactly("test"); + + verify(mockProvider).getFormatName(); + verify(mockProvider).getFileExtensions(); + } + + @Test + public void testPngProviderCreatesValidInputFormat() { + PngFormatProvider provider = new PngFormatProvider(); + ImageFigure prototype = new ImageFigure(); + + InputFormat inputFormat = provider.createInputFormat(prototype); + + assertThat(inputFormat) + .as("PNG provider should create valid input format") + .isNotNull(); + } + + @Test + public void testJpegProviderCreatesValidInputFormat() { + JpegFormatProvider provider = new JpegFormatProvider(); + ImageFigure prototype = new ImageFigure(); + + InputFormat inputFormat = provider.createInputFormat(prototype); + + assertThat(inputFormat) + .as("JPEG provider should create valid input format") + .isNotNull(); + } + + // ==================== PROVIDER SPECIFIC TESTS ==================== + + @Test + public void testPngProviderProperties() { PngFormatProvider provider = new PngFormatProvider(); assertThat(provider.getFormatName()).isEqualTo("PNG"); - assertThat(provider.getFileExtensions()).isEqualTo(new String[]{"png"}); - assertThat(provider.getMimeTypes()).isEqualTo(new String[]{"image/png"}); + assertThat(provider.getFileExtensions()).containsExactly("png"); + assertThat(provider.getMimeTypes()).containsExactly("image/png"); + assertThat(provider.getDescription()).contains("PNG"); } @Test - public void testJpegProvider() { + public void testJpegProviderProperties() { JpegFormatProvider provider = new JpegFormatProvider(); assertThat(provider.getFormatName()).isEqualTo("JPEG"); - assertThat(provider.getFileExtensions()).isEqualTo(new String[]{"jpg", "jpeg"}); + assertThat(provider.getFileExtensions()).containsExactly("jpg", "jpeg"); + assertThat(provider.getDescription()).contains("JPEG"); + } + + @Test + public void testPngProviderCreatesOutputFormat() { + PngFormatProvider provider = new PngFormatProvider(); + + OutputFormat outputFormat = provider.createOutputFormat(); + + assertThat(outputFormat) + .as("PNG provider should create output format") + .isNotNull(); + } + + @Test + public void testJpegProviderCreatesOutputFormat() { + JpegFormatProvider provider = new JpegFormatProvider(); + + OutputFormat outputFormat = provider.createOutputFormat(); + + assertThat(outputFormat) + .as("JPEG provider should create output format") + .isNotNull(); + } + + // ==================== JAVA ASSERTIONS FOR INVARIANTS ==================== + + @Test + public void testProvidersListInvariant() { + List providers = ImageFormatRegistry.getProviders(); + + assert providers != null : "Providers list should never be null"; + assert !providers.isEmpty() : "Providers list should never be empty after initialization"; + } + + @Test + public void testInputFormatsInvariant() { + ImageFigure prototype = new ImageFigure(); + List formats = ImageFormatRegistry.createInputFormats(prototype); + + assert formats != null : "Input formats list should never be null"; + + for (InputFormat format : formats) { + assert format != null : "Individual input format should never be null"; + } + } + + @Test + public void testOutputFormatsInvariant() { + List formats = ImageFormatRegistry.createOutputFormats(); + + assert formats != null : "Output formats list should never be null"; + + for (OutputFormat format : formats) { + assert format != null : "Individual output format should never be null"; + } + } + + @Test + public void testProviderPropertiesInvariant() { + for (ImageFormatProvider provider : ImageFormatRegistry.getProviders()) { + assert provider.getFormatName() != null : "Format name should never be null"; + assert !provider.getFormatName().isEmpty() : "Format name should never be empty"; + assert provider.getFileExtensions() != null : "File extensions should never be null"; + assert provider.getFileExtensions().length > 0 : "File extensions should not be empty"; + } } } diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/bdd/GivenImageFormat.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/bdd/GivenImageFormat.java new file mode 100644 index 000000000..5b7f2a330 --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/bdd/GivenImageFormat.java @@ -0,0 +1,49 @@ +package org.jhotdraw.draw.io.bdd; + +import com.tngtech.jgiven.Stage; +import com.tngtech.jgiven.annotation.ScenarioState; +import org.jhotdraw.draw.figure.ImageFigure; + +public class GivenImageFormat extends Stage { + + @ScenarioState + protected ImageFigure prototype; + + @ScenarioState + protected String fileExtension; + + public GivenImageFormat the_image_format_registry_is_initialized() { + prototype = new ImageFigure(); + return self(); + } + + public GivenImageFormat a_png_file_extension() { + fileExtension = "png"; + return self(); + } + + public GivenImageFormat a_jpeg_file_extension() { + fileExtension = "jpeg"; + return self(); + } + + public GivenImageFormat a_jpg_file_extension() { + fileExtension = "jpg"; + return self(); + } + + public GivenImageFormat an_unsupported_file_extension() { + fileExtension = "xyz"; + return self(); + } + + public GivenImageFormat an_uppercase_file_extension(String ext) { + fileExtension = ext.toUpperCase(); + return self(); + } + + public GivenImageFormat a_file_with_extension(String ext) { + fileExtension = ext; + return self(); + } +} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/bdd/ImageFormatBDDTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/bdd/ImageFormatBDDTest.java new file mode 100644 index 000000000..23b6fb50f --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/bdd/ImageFormatBDDTest.java @@ -0,0 +1,98 @@ +package org.jhotdraw.draw.io.bdd; + +import com.tngtech.jgiven.junit.ScenarioTest; +import org.junit.Test; + +/** + * BDD tests for the user story: + * "As a user, I want to be able to open various formats (PNG, JPEG) + * so I can work on top of my previous work" + */ +public class ImageFormatBDDTest extends ScenarioTest { + + @Test + public void user_can_open_png_image_to_work_on_previous_work() { + given().the_image_format_registry_is_initialized() + .and().a_png_file_extension(); + + when().the_user_opens_an_image_file(); + + then().the_format_should_be_supported() + .and().input_formats_should_be_available() + .and().the_user_can_work_on_the_image(); + } + + @Test + public void user_can_open_jpeg_image_to_work_on_previous_work() { + given().the_image_format_registry_is_initialized() + .and().a_jpeg_file_extension(); + + when().the_user_opens_an_image_file(); + + then().the_format_should_be_supported() + .and().input_formats_should_be_available() + .and().the_user_can_work_on_the_image(); + } + + @Test + public void user_can_open_jpg_image_to_work_on_previous_work() { + given().the_image_format_registry_is_initialized() + .and().a_jpg_file_extension(); + + when().the_user_opens_an_image_file(); + + then().the_format_should_be_supported() + .and().input_formats_should_be_available() + .and().the_user_can_work_on_the_image(); + } + + @Test + public void system_rejects_unsupported_image_format() { + given().the_image_format_registry_is_initialized() + .and().an_unsupported_file_extension(); + + when().the_user_checks_if_format_is_supported(); + + then().the_format_should_not_be_supported(); + } + + @Test + public void png_format_is_recognized_regardless_of_case() { + given().the_image_format_registry_is_initialized() + .and().an_uppercase_file_extension("png"); + + when().the_user_checks_if_format_is_supported(); + + then().the_format_should_be_supported(); + } + + @Test + public void jpeg_format_is_recognized_regardless_of_case() { + given().the_image_format_registry_is_initialized() + .and().an_uppercase_file_extension("jpeg"); + + when().the_user_checks_if_format_is_supported(); + + then().the_format_should_be_supported(); + } + + @Test + public void system_provides_input_formats_for_opening_images() { + given().the_image_format_registry_is_initialized(); + + when().the_user_requests_input_formats(); + + then().input_formats_should_be_available() + .and().at_least_$_input_formats_exist(2); + } + + @Test + public void system_provides_output_formats_for_saving_images() { + given().the_image_format_registry_is_initialized(); + + when().the_user_requests_output_formats(); + + then().output_formats_should_be_available() + .and().at_least_$_output_formats_exist(2); + } +} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/bdd/ThenImageFormatResult.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/bdd/ThenImageFormatResult.java new file mode 100644 index 000000000..4b79f8f16 --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/bdd/ThenImageFormatResult.java @@ -0,0 +1,77 @@ +package org.jhotdraw.draw.io.bdd; + +import com.tngtech.jgiven.Stage; +import com.tngtech.jgiven.annotation.ScenarioState; +import org.jhotdraw.draw.io.InputFormat; +import org.jhotdraw.draw.io.OutputFormat; +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +public class ThenImageFormatResult extends Stage { + + @ScenarioState + protected boolean formatSupported; + + @ScenarioState + protected List inputFormats; + + @ScenarioState + protected List outputFormats; + + public ThenImageFormatResult the_format_should_be_supported() { + assertThat(formatSupported) + .as("Format should be supported") + .isTrue(); + return self(); + } + + public ThenImageFormatResult the_format_should_not_be_supported() { + assertThat(formatSupported) + .as("Format should not be supported") + .isFalse(); + return self(); + } + + public ThenImageFormatResult input_formats_should_be_available() { + assertThat(inputFormats) + .as("Input formats should be available") + .isNotNull() + .isNotEmpty(); + return self(); + } + + public ThenImageFormatResult output_formats_should_be_available() { + assertThat(outputFormats) + .as("Output formats should be available") + .isNotNull() + .isNotEmpty(); + return self(); + } + + public ThenImageFormatResult at_least_$_input_formats_exist(int count) { + assertThat(inputFormats) + .as("Should have at least %d input formats", count) + .hasSizeGreaterThanOrEqualTo(count); + return self(); + } + + public ThenImageFormatResult at_least_$_output_formats_exist(int count) { + assertThat(outputFormats) + .as("Should have at least %d output formats", count) + .hasSizeGreaterThanOrEqualTo(count); + return self(); + } + + public ThenImageFormatResult the_user_can_work_on_the_image() { + assertThat(formatSupported) + .as("User should be able to work on supported format") + .isTrue(); + assertThat(inputFormats) + .as("Input formats should be available for editing") + .isNotNull() + .isNotEmpty(); + return self(); + } +} + diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/bdd/WhenOpeningImage.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/bdd/WhenOpeningImage.java new file mode 100644 index 000000000..2ff49eaa4 --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/io/bdd/WhenOpeningImage.java @@ -0,0 +1,51 @@ +package org.jhotdraw.draw.io.bdd; + +import com.tngtech.jgiven.Stage; +import com.tngtech.jgiven.annotation.ScenarioState; +import org.jhotdraw.draw.figure.ImageFigure; +import org.jhotdraw.draw.io.ImageFormatRegistry; +import org.jhotdraw.draw.io.InputFormat; +import org.jhotdraw.draw.io.OutputFormat; +import java.util.List; + +public class WhenOpeningImage extends Stage { + + @ScenarioState + protected ImageFigure prototype; + + @ScenarioState + protected String fileExtension; + + @ScenarioState + protected boolean formatSupported; + + @ScenarioState + protected List inputFormats; + + @ScenarioState + protected List outputFormats; + + public WhenOpeningImage the_user_checks_if_format_is_supported() { + formatSupported = ImageFormatRegistry.isFormatSupported(fileExtension); + return self(); + } + + public WhenOpeningImage the_user_requests_input_formats() { + inputFormats = ImageFormatRegistry.createInputFormats(prototype); + return self(); + } + + public WhenOpeningImage the_user_requests_output_formats() { + outputFormats = ImageFormatRegistry.createOutputFormats(); + return self(); + } + + public WhenOpeningImage the_user_opens_an_image_file() { + formatSupported = ImageFormatRegistry.isFormatSupported(fileExtension); + if (formatSupported) { + inputFormats = ImageFormatRegistry.createInputFormats(prototype); + } + return self(); + } +} +