From 1cf9c8435bec655e98b6beaa2cb01ccb96e2b9ef Mon Sep 17 00:00:00 2001
From: Robert Scott <code@humanleg.org.uk>
Date: Mon, 26 Mar 2018 20:30:48 +0100
Subject: [PATCH v2 05/28] testing: add HelpAwareOptionPaneMocker

---
 .../mockers/HelpAwareOptionPaneMocker.java         | 192 +++++++++++++++++++++
 1 file changed, 192 insertions(+)
 create mode 100644 test/unit/org/openstreetmap/josm/testutils/mockers/HelpAwareOptionPaneMocker.java

diff --git a/test/unit/org/openstreetmap/josm/testutils/mockers/HelpAwareOptionPaneMocker.java b/test/unit/org/openstreetmap/josm/testutils/mockers/HelpAwareOptionPaneMocker.java
new file mode 100644
index 000000000..a157df4e7
--- /dev/null
+++ b/test/unit/org/openstreetmap/josm/testutils/mockers/HelpAwareOptionPaneMocker.java
@@ -0,0 +1,192 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.testutils;
+
+import org.openstreetmap.josm.gui.HelpAwareOptionPane;
+
+import static org.junit.Assert.fail;
+
+import java.awt.Component;
+import javax.swing.Icon;
+import javax.swing.JOptionPane;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.OptionalInt;
+import java.util.stream.IntStream;
+
+import org.openstreetmap.josm.tools.Logging;
+
+import mockit.Mock;
+
+/**
+ * MockUp for {@link HelpAwareOptionPane} allowing a test to pre-seed uses of
+ * {@link HelpAwareOptionPane} with mock "responses". This works best with
+ * calls to {@link HelpAwareOptionPane#showOptionDialog(Component, Object, String, int, Icon,
+ * HelpAwareOptionPane.ButtonSpec[], HelpAwareOptionPane.ButtonSpec, String)} which use simple string-based
+ * {@code msg} parameters. In such a case, responses can be defined through a mapping from content
+ * {@link String}s to button indexes ({@link Integer}s) or button labels ({@link String}s). Example:
+ *
+ * <pre>
+ *      new HelpAwareOptionPaneMocker(ImmutableMap.&lt;String, Object&gt;builder()
+ *          .put("Do you want to save foo bar?", 2)
+ *          .put("Please restart JOSM to activate the downloaded plugins.", "OK")
+ *          .build()
+ *      );
+ * </pre>
+ *
+ * Testing examples with more complicated contents would require overriding
+ * {@link #getStringFromMessage(Object)} or even {@link #getMockResultForMessage(Object)} with custom
+ * logic. The class is implemented as a number of small methods with the main aim being to allow overriding
+ * of only the parts necessary for a particular case.
+ *
+ * The default {@link #getMockResultForMessage(Object)} will raise an
+ * {@link junit.framework.AssertionFailedError} on an {@link #showOptionDialog(Component, Object, String,
+ * int, Icon, HelpAwareOptionPane.ButtonSpec[], HelpAwareOptionPane.ButtonSpec, String)}
+ * activation without a matching mapping entry or if the named button doesn't exist.
+ *
+ * The public {@link #getMockResultMap()} method returns the modifiable result map to allow for situations
+ * where the desired result might need to be changed mid-test.
+ */
+public class HelpAwareOptionPaneMocker extends BaseDialogMockUp<HelpAwareOptionPane> {
+    /**
+     * Construct a {@link HelpAwareOptionPaneMocker} with an empty {@link #mockResultMap}.
+     */
+    public HelpAwareOptionPaneMocker() {
+        this(null);
+    }
+
+    /**
+     * Construct an {@link HelpAwareOptionPane} with the provided {@link #mockResultMap}.
+     * @param mockResultMap mapping of {@link HelpAwareOptionPane} {@code msg} string to
+     *      result button label or integer index.
+     */
+    public HelpAwareOptionPaneMocker(
+        final Map<String, Object> mockResultMap
+    ) {
+        super(mockResultMap);
+    }
+
+    protected String getStringFromMessage(final Object message) {
+        return message.toString();
+    }
+
+    protected Object getMockResultForMessage(final Object message) {
+        final String messageString = this.getStringFromMessage(message);
+        if (!this.getMockResultMap().containsKey(messageString)) {
+            fail("Unexpected HelpAwareOptionPane message string: " + messageString);
+        }
+        return this.getMockResultMap().get(messageString);
+    }
+
+    protected int getButtonPositionFromLabel(
+        final HelpAwareOptionPane.ButtonSpec[] options,
+        final String label
+    ) {
+        if (options == null) {
+            if (!label.equals("OK")) {
+                fail(String.format(
+                    "Only valid result for HelpAwareOptionPane with options = null is \"OK\": received %s",
+                    label
+                ));
+            }
+            return JOptionPane.OK_OPTION;
+        } else {
+            final OptionalInt optIndex = IntStream.range(0, options.length)
+                .filter(i -> options[i].text.equals(label))
+                .findFirst();
+            if (!optIndex.isPresent()) {
+                fail(String.format(
+                    "Unable to find button labeled \"%s\". Instead found %s",
+                    label,
+                    Arrays.toString(Arrays.stream(options).map((buttonSpec) -> buttonSpec.text).toArray())
+                ));
+            }
+            // buttons are numbered with 1-based indexing
+            return optIndex.getAsInt() + 1;
+        }
+    }
+
+    protected Object[] getInvocationLogEntry(
+        final Object msg,
+        final String title,
+        final Integer messageType,
+        final Icon icon,
+        final HelpAwareOptionPane.ButtonSpec[] options,
+        final HelpAwareOptionPane.ButtonSpec defaultOption,
+        final String helpTopic,
+        final Integer mockResult
+    ) {
+        return new Object[] {
+            mockResult,
+            this.getStringFromMessage(msg),
+            title
+        };
+    }
+
+    @Mock
+    protected int showOptionDialog(
+        final Component parentComponent,
+        final Object msg,
+        final String title,
+        final int messageType,
+        final Icon icon,
+        final HelpAwareOptionPane.ButtonSpec[] options,
+        final HelpAwareOptionPane.ButtonSpec defaultOption,
+        final String helpTopic
+    ) {
+        final Object result = this.getMockResultForMessage(msg);
+
+        if (result == null) {
+            fail(
+                "Invalid result for HelpAwareOptionPane: null (HelpAwareOptionPane returns"
+                + "JOptionPane.OK_OPTION for closed windows if that was the intent)"
+            );
+        }
+
+        Integer retval = null;
+        if (result instanceof String) {
+            retval = this.getButtonPositionFromLabel(options, (String) result);
+        } else if (result instanceof Integer) {
+            retval = (Integer) result;
+        } else {
+            throw new IllegalArgumentException(
+                "HelpAwareOptionPane message mapped to unsupported type of Object: " + result
+            );
+        }
+
+        // check the returned integer for validity
+        if (retval < 0) {
+            fail(String.format(
+                "Invalid result for HelpAwareOptionPane: %s (HelpAwareOptionPane returns "
+                + "JOptionPane.OK_OPTION for closed windows if that was the intent)",
+                retval
+            ));
+        } else if (retval > (options == null ? 0 : options.length)) {  // NOTE 1-based indexing
+            fail(String.format(
+                "Invalid result for HelpAwareOptionPane: %s (in call with options = %s)",
+                retval,
+                options
+            ));
+        }
+
+        Logging.info(
+            "{0} answering {1} to HelpAwareOptionPane with message {2}",
+            this.getClass().getName(),
+            retval,
+            this.getStringFromMessage(msg)
+        );
+
+        this.getInvocationLogInternal().add(this.getInvocationLogEntry(
+            msg,
+            title,
+            messageType,
+            icon,
+            options,
+            defaultOption,
+            helpTopic,
+            retval
+        ));
+
+        return retval;
+    }
+}
-- 
2.11.0

