source: josm/trunk/test/unit/org/openstreetmap/josm/testutils/mockers/ExtendedDialogMocker.java@ 17360

Last change on this file since 17360 was 17275, checked in by Don-vip, 3 years ago

see #16567 - upgrade almost all tests to JUnit 5, except those depending on WiremockRule

See https://github.com/tomakehurst/wiremock/issues/684

  • Property svn:eol-style set to native
File size: 8.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.testutils.mockers;
3
4import static org.junit.jupiter.api.Assertions.fail;
5
6import java.awt.Component;
7import java.awt.GraphicsEnvironment;
8import java.lang.reflect.Field;
9import java.util.Arrays;
10import java.util.Map;
11import java.util.NoSuchElementException;
12import java.util.Optional;
13import java.util.WeakHashMap;
14
15import org.junit.platform.commons.util.ReflectionUtils;
16import org.openstreetmap.josm.TestUtils;
17import org.openstreetmap.josm.gui.ExtendedDialog;
18import org.openstreetmap.josm.tools.Logging;
19
20import mockit.Invocation;
21import mockit.Mock;
22
23/**
24 * MockUp for {@link ExtendedDialog} allowing a test to pre-seed uses of {@link ExtendedDialog}
25 * with mock "responses". This works best with {@link ExtendedDialog}s which have their contents set
26 * through {@link ExtendedDialog#setContent(String)} as simple strings. In such a case, responses can
27 * be defined through a mapping from content {@link String}s to button indexes ({@link Integer}s) or
28 * button labels ({@link String}s). Example:
29 *
30 * <pre>
31 * new ExtendedDialogMocker(ImmutableMap.&lt;String, Object&gt;builder()
32 * .put("JOSM version 8,001 required for plugin baz_plugin.", "Download Plugin")
33 * .put("JOSM version 7,001 required for plugin dummy_plugin.", "Cancel")
34 * .put("Are you sure you want to do foo bar?", ExtendedDialog.DialogClosedOtherwise)
35 * .build()
36 * );
37 * </pre>
38 *
39 * Testing examples with more complicated contents would require overriding
40 * {@link #getString(ExtendedDialog)} or even {@link #getMockResult(ExtendedDialog)} with custom logic.
41 * The class is implemented as a number of small methods with the main aim being to allow overriding of
42 * only the parts necessary for a particular case.
43 *
44 * The default {@link #getMockResult(ExtendedDialog)} will raise an
45 * {@link AssertionError} on an {@link ExtendedDialog} activation without a
46 * matching mapping entry or if the named button doesn't exist.
47 *
48 * The public {@link #getMockResultMap()} method returns the modifiable result map to allow for situations
49 * where the desired result might need to be changed mid-test.
50 */
51public class ExtendedDialogMocker extends BaseDialogMockUp<ExtendedDialog> {
52 /**
53 * Because we're unable to add fields to the mocked class, we need to use this external global
54 * mapping to be able to keep a note of the most recently set simple String contents of each
55 * {@link ExtendedDialog} instance - {@link ExtendedDialog} doesn't store this information
56 * itself, instead converting it directly into the embedded {@link Component}.
57 */
58 protected final Map<ExtendedDialog, String> simpleStringContentMemo = new WeakHashMap<>();
59
60 /**
61 * Construct an {@link ExtendedDialogMocker} with an empty {@link #mockResultMap}.
62 */
63 public ExtendedDialogMocker() {
64 this(null);
65 }
66
67 /**
68 * Construct an {@link ExtendedDialogMocker} with the provided {@link #mockResultMap}.
69 * @param mockResultMap mapping of {@link ExtendedDialog} string contents to
70 * result button label or integer index.
71 */
72 public ExtendedDialogMocker(final Map<String, Object> mockResultMap) {
73 super(mockResultMap);
74 if (GraphicsEnvironment.isHeadless()) {
75 new WindowMocker();
76 }
77 }
78
79 protected int getButtonPositionFromLabel(final ExtendedDialog instance, final String label) {
80 final String[] bTexts = (String[]) ReflectionUtils.tryToReadFieldValue(ExtendedDialog.class, "bTexts", instance)
81 .toOptional().orElseThrow(NoSuchElementException::new);
82 final int position = Arrays.asList(bTexts).indexOf(label);
83 if (position == -1) {
84 fail("Unable to find button labeled \"" + label + "\". Instead found: " + Arrays.toString(bTexts));
85 }
86 return position;
87 }
88
89 protected String getString(final ExtendedDialog instance) {
90 return Optional.ofNullable(this.simpleStringContentMemo.get(instance))
91 .orElseGet(() -> instance.toString());
92 }
93
94 protected int getMockResult(final ExtendedDialog instance) {
95 final String stringContent = this.getString(instance);
96 final Object result = this.getMockResultMap().get(stringContent);
97
98 if (result == null) {
99 fail(
100 "Unexpected ExtendedDialog content: " + stringContent
101 );
102 } else if (result instanceof Integer) {
103 return (Integer) result;
104 } else if (result instanceof String) {
105 // buttons are numbered with 1-based indexing
106 return 1 + this.getButtonPositionFromLabel(instance, (String) result);
107 }
108
109 throw new IllegalArgumentException(
110 "ExtendedDialog contents mapped to unsupported type of Object: " + result
111 );
112 }
113
114 /**
115 * Target for overriding, similar to {@link #getMockResult} except with the implication it will only
116 * be invoked once per dialog display, therefore ideal opportunity to perform any mutating actions,
117 * e.g. making a selection on a widget.
118 * @param instance dialog instance
119 */
120 protected void act(final ExtendedDialog instance) {
121 // Override in sub-classes
122 }
123
124 protected Object[] getInvocationLogEntry(final ExtendedDialog instance, final int mockResult) {
125 return new Object[] {
126 mockResult,
127 this.getString(instance),
128 instance.getTitle()
129 };
130 }
131
132 /**
133 * A convenience method to access {@link ExtendedDialog#content} without exception-catching boilerplate
134 * @param instance dialog instance
135 * @return dialog content component
136 */
137 protected Component getContent(final ExtendedDialog instance) {
138 try {
139 return (Component) TestUtils.getPrivateField(instance, "content");
140 } catch (ReflectiveOperationException e) {
141 throw new RuntimeException(e);
142 }
143 }
144
145 @Mock
146 private void setupDialog(final Invocation invocation) {
147 if (!GraphicsEnvironment.isHeadless()) {
148 invocation.proceed();
149 }
150 // else do nothing - WindowMocker-ed Windows doesn't work well enough for some of the
151 // component constructions
152 }
153
154 @Mock
155 private void setVisible(final Invocation invocation, final boolean value) throws Throwable {
156 if (value == true) {
157 try {
158 final ExtendedDialog instance = invocation.getInvokedInstance();
159 this.act(instance);
160 final int mockResult = this.getMockResult(instance);
161 // TODO check validity of mockResult?
162 Field resultField = instance.getClass().getDeclaredField("result");
163 resultField.setAccessible(true);
164 resultField.set(instance, mockResult);
165 Logging.info(
166 "{0} answering {1} to ExtendedDialog with content {2}",
167 this.getClass().getName(),
168 mockResult,
169 this.getString(instance)
170 );
171 this.getInvocationLogInternal().add(this.getInvocationLogEntry(instance, mockResult));
172 } catch (AssertionError | NoSuchFieldException | IllegalAccessException e) {
173 // in case this exception gets ignored by the calling thread we want to signify this failure
174 // in the invocation log. it's hard to know what to add to the log in these cases as it's
175 // probably unsafe to call getInvocationLogEntry, so add the exception on its own.
176 this.getInvocationLogInternal().add(new Object[] {e});
177 throw e;
178 }
179 }
180 }
181
182 @Mock
183 private ExtendedDialog setContent(final Invocation invocation, final String message) {
184 final ExtendedDialog retval = invocation.proceed(message);
185 // must set this *after* the regular invocation else that will fall through to
186 // setContent(Component, boolean) which would overwrite it (with null)
187 this.simpleStringContentMemo.put((ExtendedDialog) invocation.getInvokedInstance(), message);
188 return retval;
189 }
190
191 @Mock
192 private ExtendedDialog setContent(final Invocation invocation, final Component content, final boolean placeContentInScrollPane) {
193 this.simpleStringContentMemo.put((ExtendedDialog) invocation.getInvokedInstance(), null);
194 return invocation.proceed(content, placeContentInScrollPane);
195 }
196}
Note: See TracBrowser for help on using the repository browser.