diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 000000000..603778e08
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,64 @@
+name: CI
+
+# Build and test on every push to the main development branches and on
+# every pull request targeting them. The workflow runs the full Maven
+# reactor build with all tests, then uploads Surefire and JGiven
+# reports as artifacts so they are inspectable from the GitHub Actions
+# run page.
+
+on:
+ push:
+ branches: [main, develop]
+ pull_request:
+ branches: [main, develop]
+ workflow_dispatch:
+
+# A new push to the same branch cancels any in-progress run on that
+# branch. Saves CI minutes when a contributor pushes a fix on top of a
+# failing build.
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build:
+ name: Build and test (JDK 11)
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+
+ steps:
+ - name: Check out source
+ uses: actions/checkout@v4
+
+ - name: Set up JDK 11 (Temurin)
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: '11'
+ cache: maven
+
+ - name: Build and run all tests
+ run: mvn -B -ntp clean install
+ env:
+ # Swing classes used in tests (JColorChooser, JPopupMenu)
+ # require headless mode on the runner since no display is
+ # attached.
+ MAVEN_OPTS: -Djava.awt.headless=true
+
+ - name: Upload Surefire test reports
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: surefire-reports
+ path: '**/target/surefire-reports/'
+ retention-days: 14
+ if-no-files-found: ignore
+
+ - name: Upload JGiven BDD reports
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: jgiven-reports
+ path: '**/target/jgiven-reports/'
+ retention-days: 14
+ if-no-files-found: ignore
diff --git a/jhotdraw-core/pom.xml b/jhotdraw-core/pom.xml
index 7c276da85..4700e5495 100644
--- a/jhotdraw-core/pom.xml
+++ b/jhotdraw-core/pom.xml
@@ -35,6 +35,18 @@
6.8.21test
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ org.mockito
+ mockito-core
+ 4.11.0
+ test
+ ${project.groupId}jhotdraw-actions
diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/SelectionColorChooserHandler.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/SelectionColorChooserHandler.java
index d5f6d56d1..bdde3a2c8 100644
--- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/SelectionColorChooserHandler.java
+++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/action/SelectionColorChooserHandler.java
@@ -31,7 +31,6 @@ public class SelectionColorChooserHandler extends AbstractSelectedAction
protected JPopupMenu popupMenu;
protected int isUpdating;
- //protected Map attributes;
/**
* Creates a new instance.
*/
@@ -40,59 +39,55 @@ public SelectionColorChooserHandler(DrawingEditor editor, AttributeKey ke
this.key = key;
this.colorChooser = colorChooser;
this.popupMenu = popupMenu;
- //colorChooser.addActionListener(this);
colorChooser.getSelectionModel().addChangeListener(this);
updateEnabledState();
}
@Override
public void actionPerformed(java.awt.event.ActionEvent evt) {
- /*
- if (evt.getActionCommand() == JColorChooser.APPROVE_SELECTION) {
- applySelectedColorToFigures();
- } else if (evt.getActionCommand() == JColorChooser.CANCEL_SELECTION) {
- }*/
popupMenu.setVisible(false);
}
protected void applySelectedColorToFigures() {
final ArrayList selectedFigures = new ArrayList<>(getView().getSelectedFigures());
- final ArrayList
+
+ com.tngtech.jgiven
+ jgiven-junit
+ 1.3.1
+ test
+
+
+ org.assertj
+ assertj-core
+ 3.24.2
+ test
+
diff --git a/jhotdraw-samples/jhotdraw-samples-misc/src/test/java/org/jhotdraw/draw/action/FillColorScenarioTest.java b/jhotdraw-samples/jhotdraw-samples-misc/src/test/java/org/jhotdraw/draw/action/FillColorScenarioTest.java
new file mode 100644
index 000000000..682817d1a
--- /dev/null
+++ b/jhotdraw-samples/jhotdraw-samples-misc/src/test/java/org/jhotdraw/draw/action/FillColorScenarioTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2026 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.draw.action;
+
+import java.awt.Color;
+
+import com.tngtech.jgiven.junit.ScenarioTest;
+import org.junit.Test;
+
+/**
+ * Lab 8 BDD scenarios for the fill-colour / opacity user story.
+ *
+ *
User story (from Lab 2):
+ *
+ * As a JHotDraw user creating SVG drawings, I want to fill a selected
+ * figure with a chosen colour and adjust its opacity, so that I can
+ * visually emphasise or de-emphasise elements in my drawing with
+ * fine-grained control.
+ *
+ *
+ *
Each scenario covers one acceptance criterion from the change
+ * request. The scenarios are written in the JGiven Given/When/Then
+ * style and report under the test class name to JGiven's HTML report
+ * after a successful build.
+ */
+public class FillColorScenarioTest
+ extends ScenarioTest {
+
+ @Test
+ public void a_single_selected_rectangle_receives_the_chosen_fill_color() {
+ given().a_single_rectangle_is_selected();
+ when().the_user_chooses_fill_color(Color.RED);
+ then().each_figure_has_fill_color(Color.RED);
+ }
+
+ @Test
+ public void all_rectangles_in_a_multi_selection_receive_the_chosen_fill_color() {
+ given().$_rectangles_are_selected(3);
+ when().the_user_chooses_fill_color(Color.BLUE);
+ then().each_figure_has_fill_color(Color.BLUE);
+ }
+
+ @Test
+ public void a_fully_transparent_pick_clears_the_fill_color() {
+ given().a_single_rectangle_is_selected()
+ .and().the_selected_rectangle_has_fill_color(Color.RED);
+ when().the_user_picks_a_fully_transparent_color();
+ then().each_figure_has_no_fill_color();
+ }
+
+ @Test
+ public void adjusting_fill_opacity_updates_the_selected_figure() {
+ given().a_single_rectangle_is_selected();
+ when().the_user_adjusts_fill_opacity_to(0.5);
+ then().each_figure_has_fill_opacity(0.5);
+ }
+
+ @Test
+ public void adjusting_fill_opacity_to_the_zero_boundary_is_accepted() {
+ given().a_single_rectangle_is_selected()
+ .and().the_selected_rectangle_has_fill_opacity(1.0);
+ when().the_user_adjusts_fill_opacity_to(0.0);
+ then().each_figure_has_fill_opacity(0.0);
+ }
+
+ @Test
+ public void choosing_a_color_with_no_selection_changes_nothing() {
+ given().no_figures_are_selected();
+ when().the_user_chooses_fill_color(Color.GREEN);
+ then().the_selection_remains_empty();
+ }
+}
diff --git a/jhotdraw-samples/jhotdraw-samples-misc/src/test/java/org/jhotdraw/draw/action/GivenSelection.java b/jhotdraw-samples/jhotdraw-samples-misc/src/test/java/org/jhotdraw/draw/action/GivenSelection.java
new file mode 100644
index 000000000..b0c113dc6
--- /dev/null
+++ b/jhotdraw-samples/jhotdraw-samples-misc/src/test/java/org/jhotdraw/draw/action/GivenSelection.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2026 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.draw.action;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.tngtech.jgiven.Stage;
+import com.tngtech.jgiven.annotation.ScenarioState;
+
+import org.jhotdraw.draw.AttributeKeys;
+import org.jhotdraw.draw.figure.Figure;
+import org.jhotdraw.samples.svg.SVGAttributeKeys;
+import org.jhotdraw.samples.svg.figures.SVGRectFigure;
+
+/**
+ * Given-stage for the Lab 8 BDD scenarios on the fill-colour / opacity
+ * feature.
+ *
+ *