Skip to content

Commit 1d95851

Browse files
committed
Remove reflective access from find/replace tests #2060
The tests for the FindReplaceDialog and FindReplaceOverlay currently use reflection to access specific UI elements. This ties the test implementations to implementation details of the production classes (i.e., specific hidden field to be present) and particularly requires the production code to contain (hidden) fields even if they would not be required just to provide according tests. This change replaces the reflective access (at the code level) with widget extraction functionality based on unique information about the widget to be searched for. This may also be considered reflective (at the configuration level), but at least it gets rid of requiring the code to contain specific fields just in order to write tests that need to extract them. Fixes #2060
1 parent f35ef3a commit 1d95851

File tree

5 files changed

+181
-81
lines changed

5 files changed

+181
-81
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Vector Informatik GmbH and others.
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Vector Informatik GmbH - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.ui.internal.findandreplace;
15+
16+
import java.util.ArrayList;
17+
import java.util.Arrays;
18+
import java.util.List;
19+
import java.util.function.Function;
20+
21+
import org.eclipse.swt.widgets.Button;
22+
import org.eclipse.swt.widgets.Combo;
23+
import org.eclipse.swt.widgets.Composite;
24+
import org.eclipse.swt.widgets.ToolBar;
25+
import org.eclipse.swt.widgets.ToolItem;
26+
import org.eclipse.swt.widgets.Widget;
27+
28+
import org.eclipse.ui.internal.findandreplace.overlay.HistoryTextWrapper;
29+
30+
public final class WidgetExtractor {
31+
32+
private final Composite container;
33+
34+
public WidgetExtractor(Composite container) {
35+
this.container= container;
36+
}
37+
38+
public HistoryTextWrapper findHistoryTextWrapperWithMessage(String includedMessageText) {
39+
List<HistoryTextWrapper> widgets= findWidgets(container, HistoryTextWrapper.class, candidate -> {
40+
String message= removeMnemonicsAndMakeLowercase(candidate.getTextBar().getMessage());
41+
return message.contains(includedMessageText);
42+
});
43+
return widgets.isEmpty() ? null : widgets.get(0);
44+
}
45+
46+
public List<Combo> findCombos() {
47+
return findWidgets(container, Combo.class, candidate -> true);
48+
}
49+
50+
public Button findButtonWithText(String includedText, String... excludedTexts) {
51+
List<Button> widgets= findWidgets(container, Button.class, candidate -> {
52+
String text= removeMnemonicsAndMakeLowercase(candidate.getText());
53+
boolean containsIncludeString= text.contains(includedText);
54+
boolean containsExcludeStrings= Arrays.stream(excludedTexts).anyMatch(it -> text.contains(it));
55+
return containsIncludeString && !containsExcludeStrings;
56+
});
57+
return widgets.isEmpty() ? null : widgets.get(0);
58+
}
59+
60+
public Button findButtonWithTooltipText(String includedToolTipText) {
61+
List<Button> widgets= findWidgets(container, Button.class, candidate -> {
62+
String toolTipText= candidate.getToolTipText();
63+
return toolTipText != null && removeMnemonicsAndMakeLowercase(toolTipText).contains(includedToolTipText);
64+
});
65+
return widgets.isEmpty() ? null : widgets.get(0);
66+
}
67+
68+
public ToolItem findToolItemWithTooltipText(String includedToolTipText, String... excludedToolTipTexts) {
69+
List<ToolItem> widgets= findWidgets(container, ToolItem.class, candidate -> {
70+
String toolTipText= removeMnemonicsAndMakeLowercase(candidate.getToolTipText());
71+
boolean containsIncludeString= toolTipText.contains(includedToolTipText);
72+
boolean containsExcludeStrings= Arrays.stream(excludedToolTipTexts).anyMatch(it -> toolTipText.contains(it));
73+
return containsIncludeString && !containsExcludeStrings;
74+
});
75+
return widgets.isEmpty() ? null : widgets.get(0);
76+
}
77+
78+
private static <T extends Widget> List<T> findWidgets(Composite container, Class<T> type, Function<T, Boolean> matcher) {
79+
List<Widget> children= new ArrayList<>();
80+
children.addAll(List.of(container.getChildren()));
81+
if (container instanceof ToolBar toolbar) {
82+
children.addAll(List.of(toolbar.getItems()));
83+
}
84+
List<T> result= new ArrayList<>();
85+
for (Widget child : children) {
86+
if (type.isInstance(child)) {
87+
@SuppressWarnings("unchecked")
88+
T typedChild= (T) child;
89+
if (matcher.apply(typedChild)) {
90+
result.add(typedChild);
91+
}
92+
}
93+
if (child instanceof Composite compositeChild) {
94+
result.addAll(findWidgets(compositeChild, type, matcher));
95+
}
96+
}
97+
return result;
98+
}
99+
100+
private static String removeMnemonicsAndMakeLowercase(String string) {
101+
return string == null ? "" : string.replaceAll("&", "").toLowerCase();
102+
}
103+
}

tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlayTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ public class FindReplaceOverlayTest extends FindReplaceUITest<OverlayAccess> {
4545
public OverlayAccess openUIFromTextViewer(TextViewer viewer) {
4646
Accessor actionAccessor= new Accessor(getFindReplaceAction(), FindReplaceAction.class);
4747
actionAccessor.invoke("showOverlayInEditor", null);
48-
Accessor overlayAccessor= new Accessor(actionAccessor.get("overlay"), "org.eclipse.ui.internal.findandreplace.overlay.FindReplaceOverlay", getClass().getClassLoader());
49-
return new OverlayAccess(getFindReplaceTarget(), overlayAccessor);
48+
FindReplaceOverlay overlay= (FindReplaceOverlay) actionAccessor.get("overlay");
49+
return new OverlayAccess(getFindReplaceTarget(), overlay);
5050
}
5151

5252
@Test

tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/internal/findandreplace/overlay/OverlayAccess.java

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import java.util.Objects;
2222
import java.util.Set;
2323
import java.util.function.Predicate;
24-
import java.util.function.Supplier;
2524
import java.util.stream.Collectors;
2625

2726
import org.eclipse.swt.SWT;
@@ -31,13 +30,12 @@
3130
import org.eclipse.swt.widgets.Shell;
3231
import org.eclipse.swt.widgets.ToolItem;
3332

34-
import org.eclipse.text.tests.Accessor;
35-
3633
import org.eclipse.jface.text.IFindReplaceTarget;
3734
import org.eclipse.jface.text.IFindReplaceTargetExtension;
3835

3936
import org.eclipse.ui.internal.findandreplace.IFindReplaceUIAccess;
4037
import org.eclipse.ui.internal.findandreplace.SearchOptions;
38+
import org.eclipse.ui.internal.findandreplace.WidgetExtractor;
4139

4240
class OverlayAccess implements IFindReplaceUIAccess {
4341
private final IFindReplaceTarget findReplaceTarget;
@@ -64,28 +62,33 @@ class OverlayAccess implements IFindReplaceUIAccess {
6462

6563
private ToolItem replaceAllButton;
6664

67-
private final Runnable closeOperation;
68-
69-
private final Accessor dialogAccessor;
65+
private final FindReplaceOverlay overlay;
7066

71-
private final Supplier<Shell> shellRetriever;
67+
private final Shell shell;
7268

73-
OverlayAccess(IFindReplaceTarget findReplaceTarget, Accessor findReplaceOverlayAccessor) {
69+
OverlayAccess(IFindReplaceTarget findReplaceTarget, FindReplaceOverlay findReplaceOverlay) {
7470
this.findReplaceTarget= findReplaceTarget;
75-
dialogAccessor= findReplaceOverlayAccessor;
76-
find= (HistoryTextWrapper) findReplaceOverlayAccessor.get("searchBar");
77-
replace= (HistoryTextWrapper) findReplaceOverlayAccessor.get("replaceBar");
78-
caseSensitive= (ToolItem) findReplaceOverlayAccessor.get("caseSensitiveSearchButton");
79-
wholeWord= (ToolItem) findReplaceOverlayAccessor.get("wholeWordSearchButton");
80-
regEx= (ToolItem) findReplaceOverlayAccessor.get("regexSearchButton");
81-
searchForward= (ToolItem) findReplaceOverlayAccessor.get("searchDownButton");
82-
searchBackward= (ToolItem) findReplaceOverlayAccessor.get("searchUpButton");
83-
closeOperation= () -> findReplaceOverlayAccessor.invoke("close", null);
84-
openReplaceDialog= (Button) findReplaceOverlayAccessor.get("replaceToggle");
85-
replaceButton= (ToolItem) findReplaceOverlayAccessor.get("replaceButton");
86-
replaceAllButton= (ToolItem) findReplaceOverlayAccessor.get("replaceAllButton");
87-
inSelection= (ToolItem) findReplaceOverlayAccessor.get("searchInSelectionButton");
88-
shellRetriever= () -> ((Shell) findReplaceOverlayAccessor.invoke("getShell", null));
71+
overlay= findReplaceOverlay;
72+
shell= overlay.getShell();
73+
WidgetExtractor widgetExtractor= new WidgetExtractor(shell);
74+
find= widgetExtractor.findHistoryTextWrapperWithMessage("find");
75+
caseSensitive= widgetExtractor.findToolItemWithTooltipText("case");
76+
wholeWord= widgetExtractor.findToolItemWithTooltipText("whole");
77+
regEx= widgetExtractor.findToolItemWithTooltipText("regular");
78+
inSelection= widgetExtractor.findToolItemWithTooltipText("selected");
79+
searchForward= widgetExtractor.findToolItemWithTooltipText("forward");
80+
searchBackward= widgetExtractor.findToolItemWithTooltipText("backward");
81+
openReplaceDialog= widgetExtractor.findButtonWithTooltipText("toggle");
82+
extractReplaceWidgets();
83+
}
84+
85+
private void extractReplaceWidgets() {
86+
if (!isReplaceDialogOpen() && Objects.nonNull(openReplaceDialog)) {
87+
WidgetExtractor widgetExtractor= new WidgetExtractor(shell);
88+
replace= widgetExtractor.findHistoryTextWrapperWithMessage("replace");
89+
replaceButton= widgetExtractor.findToolItemWithTooltipText("replace", "all");
90+
replaceAllButton= widgetExtractor.findToolItemWithTooltipText("replace all");
91+
}
8992
}
9093

9194
private void restoreInitialConfiguration() {
@@ -100,12 +103,12 @@ private void restoreInitialConfiguration() {
100103
public void closeAndRestore() {
101104
restoreInitialConfiguration();
102105
assertInitialConfiguration();
103-
closeOperation.run();
106+
overlay.close();
104107
}
105108

106109
@Override
107110
public void close() {
108-
closeOperation.run();
111+
overlay.close();
109112
}
110113

111114
@Override
@@ -234,15 +237,13 @@ public void performReplace() {
234237
}
235238

236239
public boolean isReplaceDialogOpen() {
237-
return dialogAccessor.getBoolean("replaceBarOpen");
240+
return replace != null;
238241
}
239242

240243
public void openReplaceDialog() {
241244
if (!isReplaceDialogOpen() && Objects.nonNull(openReplaceDialog)) {
242245
openReplaceDialog.notifyListeners(SWT.Selection, null);
243-
replace= (HistoryTextWrapper) dialogAccessor.get("replaceBar");
244-
replaceButton= (ToolItem) dialogAccessor.get("replaceButton");
245-
replaceAllButton= (ToolItem) dialogAccessor.get("replaceAllButton");
246+
extractReplaceWidgets();
246247
}
247248
}
248249

@@ -309,15 +310,14 @@ public void assertEnabled(SearchOptions option) {
309310

310311
@Override
311312
public boolean isShown() {
312-
return shellRetriever.get() != null && shellRetriever.get().isVisible();
313+
return !shell.isDisposed() && shell.isVisible();
313314
}
314315

315316
@Override
316317
public boolean hasFocus() {
317-
Shell overlayShell= shellRetriever.get();
318-
Control focusControl= overlayShell.getDisplay().getFocusControl();
318+
Control focusControl= shell.getDisplay().getFocusControl();
319319
Shell focusControlShell= focusControl != null ? focusControl.getShell() : null;
320-
return focusControlShell == overlayShell;
320+
return focusControlShell == shell;
321321
}
322322

323323
}

tests/org.eclipse.ui.workbench.texteditor.tests/src/org/eclipse/ui/workbench/texteditor/tests/DialogAccess.java

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -18,81 +18,75 @@
1818

1919
import java.util.Arrays;
2020
import java.util.Set;
21-
import java.util.function.Supplier;
2221
import java.util.stream.Collectors;
2322

2423
import org.eclipse.swt.SWT;
2524
import org.eclipse.swt.graphics.Point;
2625
import org.eclipse.swt.widgets.Button;
2726
import org.eclipse.swt.widgets.Combo;
27+
import org.eclipse.swt.widgets.Control;
2828
import org.eclipse.swt.widgets.Event;
2929
import org.eclipse.swt.widgets.Shell;
3030

31-
import org.eclipse.text.tests.Accessor;
31+
import org.eclipse.jface.dialogs.Dialog;
3232

3333
import org.eclipse.jface.text.IFindReplaceTarget;
3434
import org.eclipse.jface.text.IFindReplaceTargetExtension;
3535

3636
import org.eclipse.ui.internal.findandreplace.IFindReplaceUIAccess;
3737
import org.eclipse.ui.internal.findandreplace.SearchOptions;
38+
import org.eclipse.ui.internal.findandreplace.WidgetExtractor;
3839

3940
class DialogAccess implements IFindReplaceUIAccess {
4041

4142
private final IFindReplaceTarget findReplaceTarget;
4243

43-
Combo findCombo;
44+
private final Dialog findReplaceDialog;
4445

45-
Combo replaceCombo;
46+
private final Combo findCombo;
4647

47-
Button forwardRadioButton;
48+
private final Combo replaceCombo;
4849

49-
Button globalRadioButton;
50+
private final Button forwardRadioButton;
5051

51-
Button searchInRangeRadioButton;
52+
private final Button globalRadioButton;
5253

53-
Button caseCheckBox;
54+
private final Button searchInRangeRadioButton;
5455

55-
Button wrapCheckBox;
56+
private final Button caseCheckBox;
5657

57-
Button wholeWordCheckBox;
58+
private final Button wrapCheckBox;
5859

59-
Button incrementalCheckBox;
60+
private final Button wholeWordCheckBox;
6061

61-
Button regExCheckBox;
62+
private final Button incrementalCheckBox;
6263

63-
Button findButton;
64+
private final Button regExCheckBox;
6465

65-
Button replaceButton;
66+
private final Button replaceButton;
6667

67-
Button replaceFindButton;
68+
private final Button replaceFindButton;
6869

69-
Button replaceAllButton;
70+
private final Button replaceAllButton;
7071

71-
private Supplier<Shell> shellRetriever;
72-
73-
private Runnable closeOperation;
74-
75-
Accessor dialogAccessor;
76-
77-
DialogAccess(IFindReplaceTarget findReplaceTarget, Accessor findReplaceDialogAccessor) {
72+
DialogAccess(IFindReplaceTarget findReplaceTarget, Dialog findReplaceDialog) {
7873
this.findReplaceTarget= findReplaceTarget;
79-
dialogAccessor= findReplaceDialogAccessor;
80-
findCombo= (Combo) findReplaceDialogAccessor.get("fFindField");
81-
replaceCombo= (Combo) findReplaceDialogAccessor.get("fReplaceField");
82-
forwardRadioButton= (Button) findReplaceDialogAccessor.get("fForwardRadioButton");
83-
globalRadioButton= (Button) findReplaceDialogAccessor.get("fGlobalRadioButton");
84-
searchInRangeRadioButton= (Button) findReplaceDialogAccessor.get("fSelectedRangeRadioButton");
85-
caseCheckBox= (Button) findReplaceDialogAccessor.get("fCaseCheckBox");
86-
wrapCheckBox= (Button) findReplaceDialogAccessor.get("fWrapCheckBox");
87-
wholeWordCheckBox= (Button) findReplaceDialogAccessor.get("fWholeWordCheckBox");
88-
incrementalCheckBox= (Button) findReplaceDialogAccessor.get("fIncrementalCheckBox");
89-
regExCheckBox= (Button) findReplaceDialogAccessor.get("fIsRegExCheckBox");
90-
shellRetriever= () -> ((Shell) findReplaceDialogAccessor.get("fActiveShell"));
91-
closeOperation= () -> findReplaceDialogAccessor.invoke("close", null);
92-
findButton= (Button) findReplaceDialogAccessor.get("fFindNextButton");
93-
replaceButton= (Button) findReplaceDialogAccessor.get("fReplaceSelectionButton");
94-
replaceFindButton= (Button) findReplaceDialogAccessor.get("fReplaceFindButton");
95-
replaceAllButton= (Button) findReplaceDialogAccessor.get("fReplaceAllButton");
74+
this.findReplaceDialog= findReplaceDialog;
75+
WidgetExtractor widgetExtractor= new WidgetExtractor(findReplaceDialog.getShell());
76+
findCombo= widgetExtractor.findCombos().get(0);
77+
replaceCombo= widgetExtractor.findCombos().get(1);
78+
forwardRadioButton= widgetExtractor.findButtonWithText("forward");
79+
globalRadioButton= widgetExtractor.findButtonWithText("all", "select", "replace");
80+
searchInRangeRadioButton= widgetExtractor.findButtonWithText("selected");
81+
caseCheckBox= widgetExtractor.findButtonWithText("case");
82+
wrapCheckBox= widgetExtractor.findButtonWithText("wrap");
83+
wholeWordCheckBox= widgetExtractor.findButtonWithText("whole");
84+
incrementalCheckBox= widgetExtractor.findButtonWithText("incremental");
85+
regExCheckBox= widgetExtractor.findButtonWithText("regular");
86+
87+
replaceButton= widgetExtractor.findButtonWithText("replace", "find");
88+
replaceFindButton= widgetExtractor.findButtonWithText("replace/find");
89+
replaceAllButton= widgetExtractor.findButtonWithText("replace all");
9690
}
9791

9892
void restoreInitialConfiguration() {
@@ -153,17 +147,19 @@ public void unselect(SearchOptions option) {
153147
public void closeAndRestore() {
154148
restoreInitialConfiguration();
155149
assertInitialConfiguration();
156-
closeOperation.run();
150+
findReplaceDialog.close();
157151
}
158152

159153
@Override
160154
public void close() {
161-
closeOperation.run();
155+
findReplaceDialog.close();
162156
}
163157

164158
@Override
165159
public boolean hasFocus() {
166-
return shellRetriever.get() != null;
160+
Control focusControl= findReplaceDialog.getShell().getDisplay().getFocusControl();
161+
Shell focusControlShell= focusControl != null ? focusControl.getShell() : null;
162+
return focusControlShell == findReplaceDialog.getShell();
167163
}
168164

169165
@Override
@@ -296,7 +292,7 @@ private Set<SearchOptions> getSelectedOptions() {
296292

297293
@Override
298294
public boolean isShown() {
299-
return shellRetriever.get() != null;
295+
return findReplaceDialog.getShell().isVisible();
300296
}
301297

302298
}

0 commit comments

Comments
 (0)