Ticket #16010: v2-0004-testing-add-JOptionPaneSimpleMocker.patch

File v2-0004-testing-add-JOptionPaneSimpleMocker.patch, 12.7 KB (added by ris, 3 years ago)
  • src/org/openstreetmap/josm/gui/ConditionalOptionPaneUtil.java

    From 339b32d60154c6f8858029889365db93435d7d35 Mon Sep 17 00:00:00 2001
    From: Robert Scott <code@humanleg.org.uk>
    Date: Sun, 18 Mar 2018 11:48:24 +0000
    Subject: [PATCH v2 04/28] testing: add JOptionPaneSimpleMocker
    
    Unfortunately also requires making ConditionalOptionPaneUtil.MessagePanel
    public.
    ---
     .../josm/gui/ConditionalOptionPaneUtil.java        |   2 +-
     .../testutils/mockers/JOptionPaneSimpleMocker.java | 304 +++++++++++++++++++++
     2 files changed, 305 insertions(+), 1 deletion(-)
     create mode 100644 test/unit/org/openstreetmap/josm/testutils/mockers/JOptionPaneSimpleMocker.java
    
    diff --git a/src/org/openstreetmap/josm/gui/ConditionalOptionPaneUtil.java b/src/org/openstreetmap/josm/gui/ConditionalOptionPaneUtil.java
    index 0d4b25e92..0f4d3ac0a 100644
    a b public final class ConditionalOptionPaneUtil { 
    264264     * a checkbox for enabling/disabling this particular dialog.
    265265     *
    266266     */
    267     static class MessagePanel extends JPanel {
     267    public static class MessagePanel extends JPanel {
    268268        private final JRadioButton cbShowPermanentDialog = new JRadioButton(NotShowAgain.PERMANENT.getLabel());
    269269        private final JRadioButton cbShowSessionDialog = new JRadioButton(NotShowAgain.SESSION.getLabel());
    270270        private final JRadioButton cbShowImmediateDialog = new JRadioButton(NotShowAgain.OPERATION.getLabel());
  • new file test/unit/org/openstreetmap/josm/testutils/mockers/JOptionPaneSimpleMocker.java

    diff --git a/test/unit/org/openstreetmap/josm/testutils/mockers/JOptionPaneSimpleMocker.java b/test/unit/org/openstreetmap/josm/testutils/mockers/JOptionPaneSimpleMocker.java
    new file mode 100644
    index 000000000..256ee8ba0
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.testutils;
     3
     4import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil.MessagePanel;
     5
     6import static org.junit.Assert.fail;
     7
     8import java.awt.Component;
     9import javax.swing.Icon;
     10import javax.swing.JOptionPane;
     11
     12import java.util.Arrays;
     13import java.util.Map;
     14import java.util.WeakHashMap;
     15
     16import org.openstreetmap.josm.tools.Logging;
     17
     18import com.google.common.collect.ImmutableMap;
     19import com.google.common.primitives.Ints;
     20
     21import mockit.Invocation;
     22import mockit.Mock;
     23import mockit.MockUp;
     24
     25/**
     26 * MockUp for {@link JOptionPane} allowing a test to pre-seed uses of {@link JOptionPane}'s
     27 * {@code showInputDialog(...)}, {@code showMessageDialog(...)} and {@code showConfirmDialog(...)}
     28 * with mock "responses". This works best with calls which use simple string-based {@code message}
     29 * parameters. In such a case, responses can be defined through a mapping from content {@link String}s
     30 * to button integer codes ({@link Integer}s) in the case of {@code showConfirmDialog(...)} calls or
     31 * arbitrary Objects ( but probably {@link String}s) in the case of {@code showInputDialog(...)} calls.
     32 * {@code showMessageDialog(...)} calls' contents should be mapped to {@link JOptionPane.OK_OPTION}.
     33 * Example:
     34 *
     35 * <pre>
     36 *      new JOptionPaneSimpleMocker(ImmutableMap.of(
     37 *          "Number of tags to delete", "17",  // a showInputDialog(...) call
     38 *          "Please select the row to edit.", JOptionPane.OK_OPTION,  // a showMessageDialog(...) call
     39 *          "Do you want to save foo bar?", JOptionPane.CANCEL_OPTION  // a showConfirmDialog(...) call
     40 *      ));
     41 * </pre>
     42 *
     43 * Testing examples with more complicated contents would require overriding
     44 * {@link #getStringFromMessage(Object)} or even {@link #getMockResultForMessage(Object)} with custom logic.
     45 * The class is implemented as a number of small methods with the main aim being to allow overriding of
     46 * only the parts necessary for a particular case.
     47 *
     48 * The default {@link #getMockResultForMessage(Object)} will raise an
     49 * {@link junit.framework.AssertionFailedError} on an activation without a matching mapping entry or if
     50 * the mapped result value is invalid for the call.
     51 *
     52 * The public {@link #getMockResultMap()} method returns the modifiable result map to allow for situations
     53 * where the desired result might need to be changed mid-test.
     54 *
     55 * This class should also work with dialogs shown using
     56 * {@link org.openstreetmap.josm.gui.ConditionalOptionPaneUtil}.
     57 *
     58 * NOTE that this class does NOT handle {@code showOptionDialog(...)} calls or direct {@link JOptionPane}
     59 * instantiations. These are probably too flexible to be universally mocked with a "Simple" interface and
     60 * are probably best handled with case-specific mockers.
     61 */
     62public class JOptionPaneSimpleMocker extends BaseDialogMockUp<JOptionPane> {
     63    protected static final Map<Integer, int[]> optionTypePermittedResults = ImmutableMap.of(
     64        JOptionPane.YES_NO_OPTION, new int[] {
     65            JOptionPane.YES_OPTION,
     66            JOptionPane.NO_OPTION,
     67            JOptionPane.CLOSED_OPTION
     68        },
     69        JOptionPane.YES_NO_CANCEL_OPTION, new int[] {
     70            JOptionPane.YES_OPTION,
     71            JOptionPane.NO_OPTION,
     72            JOptionPane.CANCEL_OPTION,
     73            JOptionPane.CLOSED_OPTION
     74        },
     75        JOptionPane.OK_CANCEL_OPTION, new int[] {
     76            JOptionPane.OK_OPTION,
     77            JOptionPane.CANCEL_OPTION,
     78            JOptionPane.CLOSED_OPTION
     79        }
     80    );
     81
     82    protected final MessagePanelMocker messagePanelMocker;
     83
     84    /**
     85     * Construct a {@link JOptionPaneSimpleMocker} with an empty {@link #mockResultMap}.
     86     */
     87    public JOptionPaneSimpleMocker() {
     88        this(null);
     89    }
     90
     91    /**
     92     * Construct an {@link JOptionPaneSimpleMocker} with the provided {@link #mockResultMap} and a
     93     * default {@link #MessagePanelMocker}.
     94     * @param mockResultMap mapping of {@link JOptionPaneSimpleMocker} {@code message} string to
     95     *      result Object.
     96     */
     97    public JOptionPaneSimpleMocker(
     98        final Map<String, Object> mockResultMap
     99    ) {
     100        this(mockResultMap, null);
     101    }
     102
     103    /**
     104     * Construct an {@link JOptionPaneSimpleMocker} with the provided {@link #mockResultMap} and the
     105     * provided {@link #MessagePanelMocker} instance.
     106     * @param mockResultMap mapping of {@link JOptionPaneSimpleMocker} {@code message} string to
     107     *      result Object.
     108     * @param messagePanelMocker {@link #MessagePanelMocker} instace to use for {@link org.openstreetmap.josm.gui.ConditionalOptionPaneUtil}
     109     *      message-string retrieval.
     110     */
     111    public JOptionPaneSimpleMocker(
     112        final Map<String, Object> mockResultMap,
     113        final MessagePanelMocker messagePanelMocker
     114    ) {
     115        super(mockResultMap);
     116        this.messagePanelMocker = messagePanelMocker != null ? messagePanelMocker : new MessagePanelMocker();
     117    }
     118
     119    protected String getStringFromOriginalMessage(final Object originalMessage) {
     120        return originalMessage.toString();
     121    }
     122
     123    protected String getStringFromMessage(final Object message) {
     124        final Object originalMessage = message instanceof MessagePanel ?
     125            this.messagePanelMocker.getOriginalMessage((MessagePanel) message) : message;
     126        return this.getStringFromOriginalMessage(originalMessage);
     127    }
     128
     129    protected Object getMockResultForMessage(final Object message) {
     130        final String messageString = this.getStringFromMessage(message);
     131        if (!this.getMockResultMap().containsKey(messageString)) {
     132            fail("Unexpected JOptionPane message string: " + messageString);
     133        }
     134        return this.getMockResultMap().get(messageString);
     135    }
     136
     137    protected Object[] getInvocationLogEntry(
     138        final Object message,
     139        final String title,
     140        final Integer optionType,
     141        final Integer messageType,
     142        final Icon icon,
     143        final Object[] selectionValues,
     144        final Object initialSelectionValue,
     145        final Object mockResult
     146    ) {
     147        return new Object[] {
     148            mockResult,
     149            this.getStringFromMessage(message),
     150            title
     151        };
     152    }
     153
     154    @Mock
     155    protected Object showInputDialog(
     156        final Component parentComponent,
     157        final Object message,
     158        final String title,
     159        final int messageType,
     160        final Icon icon,
     161        final Object[] selectionValues,
     162        final Object initialSelectionValue
     163    ) {
     164        final Object result = this.getMockResultForMessage(message);
     165        if (selectionValues == null) {
     166            if (!(result instanceof String)) {
     167                fail(String.format(
     168                    "Only valid result type for showInputDialog with null selectionValues is String: received %s",
     169                    result
     170                ));
     171            }
     172        } else {
     173            if (!Arrays.asList(selectionValues).contains(result)) {
     174                fail(String.format(
     175                    "Result for showInputDialog not present in selectionValues: %s",
     176                    result
     177                ));
     178            }
     179        }
     180
     181        Logging.info(
     182            "{0} answering {1} to showInputDialog with message {2}",
     183            this.getClass().getName(),
     184            result,
     185            this.getStringFromMessage(message)
     186        );
     187
     188        this.getInvocationLogInternal().add(this.getInvocationLogEntry(
     189            message,
     190            title,
     191            null,
     192            messageType,
     193            icon,
     194            selectionValues,
     195            initialSelectionValue,
     196            result
     197        ));
     198
     199        return result;
     200    }
     201
     202    @Mock
     203    protected void showMessageDialog(
     204        final Component parentComponent,
     205        final Object message,
     206        final String title,
     207        final int messageType,
     208        final Icon icon
     209    ) {
     210        // why look up a "result" for a message dialog which can only have one possible result? it's
     211        // a good opportunity to assert its contents
     212        final Object result = this.getMockResultForMessage(message);
     213        if (!(result instanceof Integer && (int) result == JOptionPane.OK_OPTION)) {
     214            fail(String.format(
     215                "Only valid result for showMessageDialog is %d: received %s",
     216                JOptionPane.OK_OPTION,
     217                result
     218            ));
     219        }
     220
     221        Logging.info(
     222            "{0} answering {1} to showMessageDialog with message {2}",
     223            this.getClass().getName(),
     224            result,
     225            this.getStringFromMessage(message)
     226        );
     227
     228        this.getInvocationLogInternal().add(this.getInvocationLogEntry(
     229            message,
     230            title,
     231            null,
     232            messageType,
     233            icon,
     234            null,
     235            null,
     236            JOptionPane.OK_OPTION
     237        ));
     238    }
     239
     240    @Mock
     241    protected int showConfirmDialog(
     242        final Component parentComponent,
     243        final Object message,
     244        final String title,
     245        final int optionType,
     246        final int messageType,
     247        final Icon icon
     248    ) {
     249        final Object result = this.getMockResultForMessage(message);
     250        if (!(result instanceof Integer && Ints.contains(optionTypePermittedResults.get(optionType), (int) result))) {
     251            fail(String.format(
     252                "Invalid result for showConfirmDialog with optionType %d: %s",
     253                optionType,
     254                result
     255            ));
     256        }
     257
     258        Logging.info(
     259            "{0} answering {1} to showConfirmDialog with message {2}",
     260            this.getClass().getName(),
     261            result,
     262            this.getStringFromMessage(message)
     263        );
     264
     265        this.getInvocationLogInternal().add(this.getInvocationLogEntry(
     266            message,
     267            title,
     268            optionType,
     269            messageType,
     270            icon,
     271            null,
     272            null,
     273            result
     274        ));
     275
     276        return (int) result;
     277    }
     278
     279    /**
     280     * MockUp for {@link MessagePanel} to allow mocking to work with ConditionalOptionPaneUtil dialogs
     281     */
     282    public static class MessagePanelMocker extends MockUp<MessagePanel> {
     283        protected final Map<MessagePanel, Object> originalMessageMemo = new WeakHashMap<MessagePanel, Object>();
     284
     285        @Mock
     286        private void $init(
     287            final Invocation invocation,
     288            final Object message,
     289            final boolean displayImmediateOption
     290        ) {
     291            this.originalMessageMemo.put(
     292                (MessagePanel) invocation.getInvokedInstance(),
     293                message
     294            );
     295            invocation.proceed();
     296        }
     297
     298        public Object getOriginalMessage(final MessagePanel instance) {
     299            return this.originalMessageMemo.get(instance);
     300        }
     301
     302        /* TODO also allow mocking of getNotShowAgain() */
     303    }
     304}