source: josm/trunk/test/unit/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelperTest.java@ 18842

Last change on this file since 18842 was 18842, checked in by taylor.smock, 9 months ago

Fix #23191: NPE in AddTagsDialog

This is caused when a new layer is added via Remote Control while the add tag
dialog is open.

  • Property svn:eol-style set to native
File size: 8.7 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.properties;
3
4import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
5import static org.junit.jupiter.api.Assertions.assertEquals;
6import static org.junit.jupiter.api.Assertions.assertFalse;
7import static org.junit.jupiter.api.Assertions.assertNotNull;
8import static org.junit.jupiter.api.Assertions.assertTrue;
9
10import java.awt.GraphicsEnvironment;
11import java.lang.reflect.Field;
12import java.lang.reflect.Method;
13import java.util.ArrayList;
14import java.util.Arrays;
15import java.util.Collections;
16import java.util.HashMap;
17import java.util.List;
18import java.util.Map;
19import java.util.concurrent.Future;
20import java.util.concurrent.atomic.AtomicBoolean;
21import java.util.function.Function;
22import java.util.stream.Collectors;
23
24import javax.swing.JOptionPane;
25import javax.swing.JTable;
26import javax.swing.table.DefaultTableModel;
27
28import org.awaitility.Awaitility;
29import org.awaitility.Durations;
30import org.junit.jupiter.api.Test;
31import org.openstreetmap.josm.TestUtils;
32import org.openstreetmap.josm.data.coor.LatLon;
33import org.openstreetmap.josm.data.osm.DataSet;
34import org.openstreetmap.josm.data.osm.Node;
35import org.openstreetmap.josm.data.osm.OsmDataManager;
36import org.openstreetmap.josm.data.osm.OsmPrimitive;
37import org.openstreetmap.josm.data.osm.Way;
38import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem;
39import org.openstreetmap.josm.gui.ExtendedDialog;
40import org.openstreetmap.josm.gui.MainApplication;
41import org.openstreetmap.josm.gui.dialogs.properties.TagEditHelper.AddTagsDialog;
42import org.openstreetmap.josm.gui.layer.OsmDataLayer;
43import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
44import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
45import org.openstreetmap.josm.spi.preferences.Config;
46import org.openstreetmap.josm.testutils.annotations.Projection;
47import org.openstreetmap.josm.testutils.annotations.Territories;
48import org.openstreetmap.josm.testutils.mockers.WindowMocker;
49import org.openstreetmap.josm.tools.JosmRuntimeException;
50
51import mockit.Mock;
52import mockit.MockUp;
53
54/**
55 * Unit tests of {@link TagEditHelper} class.
56 */
57@Projection
58@Territories
59class TagEditHelperTest {
60 private static TagEditHelper newTagEditHelper() {
61 DefaultTableModel propertyData = new DefaultTableModel();
62 JTable tagTable = new JTable(propertyData);
63 Map<String, Map<String, Integer>> valueCount = new HashMap<>();
64 return new TagEditHelper(tagTable, propertyData, valueCount);
65 }
66
67 /**
68 * Checks that autocompleting list items are sorted correctly.
69 */
70 @Test
71 void testAcItemComparator() {
72 List<AutoCompletionItem> list = new ArrayList<>();
73 list.add(new AutoCompletionItem("Bing Sat"));
74 list.add(new AutoCompletionItem("survey"));
75 list.add(new AutoCompletionItem("Bing"));
76 list.add(new AutoCompletionItem("digitalglobe"));
77 list.add(new AutoCompletionItem("bing"));
78 list.add(new AutoCompletionItem("DigitalGlobe"));
79 list.sort(TagEditHelper.DEFAULT_AC_ITEM_COMPARATOR);
80 assertEquals(Arrays.asList("Bing", "bing", "Bing Sat", "digitalglobe", "DigitalGlobe", "survey"),
81 list.stream().map(AutoCompletionItem::getValue).collect(Collectors.toList()));
82 }
83
84 /**
85 * Unit test of {@link TagEditHelper#containsDataKey}.
86 */
87 @Test
88 void testContainsDataKey() {
89 assertFalse(newTagEditHelper().containsDataKey("foo"));
90 // TODO: complete test
91 }
92
93 /**
94 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/18764>#18764</a>
95 *
96 * @throws Exception if any error occurs
97 */
98 @Test
99 void testTicket18764() throws Exception {
100 testIcon("*[building] ⧉ *[highway] { text: tr(\"Building crossing highway\"); }", ds -> {
101 Way way = TestUtils.newWay("", new Node(LatLon.NORTH_POLE), new Node(LatLon.SOUTH_POLE));
102 way.getNodes().forEach(ds::addPrimitive);
103 return way;
104 }, "highway", "");
105 }
106
107 /**
108 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/18798>#18798</a>
109 *
110 * @throws Exception if any error occurs
111 */
112 @Test
113 void testTicket18798() throws Exception {
114 testIcon("node:righthandtraffic[junction=roundabout] { text: tr(\"Roundabout node\"); }", ds -> {
115 Node node = new Node(LatLon.NORTH_POLE);
116 ds.addPrimitive(node);
117 return node;
118 }, "junction", "roundabout");
119 }
120
121 /**
122 * Non-regression test for <a href="https://josm.openstreetmap.de/ticket/23191>#23191</a>
123 */
124 @Test
125 void testTicket23191() {
126 TestUtils.assumeWorkingJMockit();
127 final TagEditHelper tagEditHelper = newTagEditHelper();
128 final DataSet original = new DataSet();
129 MainApplication.getLayerManager().addLayer(new OsmDataLayer(original, "TagEditHelperTest.testTicket23191_1", null));
130 final Node toSelect = TestUtils.newNode("");
131 original.addPrimitive(toSelect);
132 original.setSelected(toSelect);
133 assertEquals(1, OsmDataManager.getInstance().getInProgressISelection().size());
134 assertTrue(OsmDataManager.getInstance().getInProgressISelection().contains(toSelect));
135
136 final AtomicBoolean canContinue = new AtomicBoolean();
137 final AtomicBoolean showingDialog = new AtomicBoolean();
138
139 // Instantiate the AddTagsDialog where we don't have to worry about race conditions
140 tagEditHelper.sel = OsmDataManager.getInstance().getInProgressSelection();
141 final AddTagsDialog addTagsDialog = tagEditHelper.getAddTagsDialog();
142 tagEditHelper.resetSelection();
143 new MockUp<TagEditHelper>() {
144 @Mock
145 public AddTagsDialog getAddTagsDialog() {
146 return addTagsDialog;
147 }
148 };
149
150 new MockUp<AddTagsDialog>() {
151 @Mock
152 public ExtendedDialog showDialog() {
153 showingDialog.set(true);
154 while (!canContinue.get()) {
155 synchronized (canContinue) {
156 try {
157 canContinue.wait();
158 } catch (InterruptedException e) {
159 throw new JosmRuntimeException(e);
160 }
161 }
162 }
163 return null;
164 }
165
166 @Mock
167 public int getValue() {
168 return 1;
169 }
170 };
171
172 // Avoid showing the JOption pane
173 Config.getPref().putBoolean("message.properties.selection-changed", false);
174 Config.getPref().putInt("message.properties.selection-changed.value", JOptionPane.YES_OPTION);
175
176 // "Open" the tag edit dialog -- this should technically be in the EDT, but we are mocking the UI parts out,
177 // since the EDT does allow new EDT runnables when showing the add tag dialog
178 Future<?> tagFuture = MainApplication.worker.submit(tagEditHelper::addTag);
179
180 Awaitility.await().atMost(Durations.ONE_SECOND).untilTrue(showingDialog);
181 // This is what remote control will effectively do
182 MainApplication.getLayerManager().addLayer(new OsmDataLayer(new DataSet(), "TagEditHelperTest.testTicket23191_2", null));
183 tagEditHelper.resetSelection();
184
185 // Enter key=value
186 addTagsDialog.keys.setText("building");
187 addTagsDialog.values.setText("yes");
188
189 // Close the tag edit dialog
190 synchronized (canContinue) {
191 canContinue.set(true);
192 canContinue.notifyAll();
193 }
194
195 assertDoesNotThrow(() -> tagFuture.get());
196 }
197
198 void testIcon(String cssString, Function<DataSet, OsmPrimitive> prepare, String key, String value) throws Exception {
199 TestUtils.assumeWorkingJMockit();
200 if (GraphicsEnvironment.isHeadless()) {
201 new WindowMocker();
202 }
203 MapCSSStyleSource css = new MapCSSStyleSource(cssString);
204 css.loadStyleSource();
205 MapPaintStyles.addStyle(css);
206 DataSet ds = new DataSet();
207 final OsmPrimitive primitive = prepare.apply(ds);
208 OsmDataManager.getInstance().setActiveDataSet(ds);
209 MainApplication.getLayerManager().addLayer(new OsmDataLayer(ds, "Test Layer", null));
210 TagEditHelper helper = newTagEditHelper();
211 Field sel = TagEditHelper.class.getDeclaredField("sel");
212 sel.set(helper, Collections.singletonList(primitive));
213 AddTagsDialog addTagsDialog = helper.getAddTagsDialog();
214 Method findIcon = TagEditHelper.AbstractTagsDialog.class.getDeclaredMethod("findIcon", String.class, String.class);
215 findIcon.setAccessible(true);
216 Object val = findIcon.invoke(addTagsDialog, key, value);
217 assertNotNull(val);
218 }
219}
Note: See TracBrowser for help on using the repository browser.