Skip to content

Commit 7ac683e

Browse files
committed
[Win32] Move tests using internal API into the SWT fragment
The TabFolder and Tree tests in the test bundle used internal API such as OS and RECT, which caused discouraged access warnings. Tests that need native messages or synchronous pixel capture move to the win32 fragment, where internal API is fine. Control.print and GC.copyArea cannot replace that capture, as they return blank or stale pixels for a window off-screen. Assisted-by: multiple AI agents and layers of automated tooling 🤖
1 parent 2503f3a commit 7ac683e

4 files changed

Lines changed: 248 additions & 192 deletions

File tree

tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/tests/win32/widgets/Test_org_eclipse_swt_widgets_TabFolder.java renamed to bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/widgets/TabFolderWin32Tests.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* Contributors:
1212
* Lars Vogel - initial API and implementation
1313
*******************************************************************************/
14-
package org.eclipse.swt.tests.win32.widgets;
14+
package org.eclipse.swt.widgets;
1515

1616
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
1717
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -25,24 +25,21 @@
2525
import org.eclipse.swt.graphics.Color;
2626
import org.eclipse.swt.graphics.GC;
2727
import org.eclipse.swt.graphics.Image;
28+
import org.eclipse.swt.internal.PlatformSpecificExecutionExtension;
2829
import org.eclipse.swt.internal.win32.OS;
2930
import org.eclipse.swt.internal.win32.RECT;
30-
import org.eclipse.swt.widgets.Composite;
31-
import org.eclipse.swt.widgets.Control;
32-
import org.eclipse.swt.widgets.Display;
33-
import org.eclipse.swt.widgets.Shell;
34-
import org.eclipse.swt.widgets.TabFolder;
35-
import org.eclipse.swt.widgets.TabItem;
3631
import org.junit.jupiter.api.AfterEach;
3732
import org.junit.jupiter.api.BeforeEach;
3833
import org.junit.jupiter.api.Test;
34+
import org.junit.jupiter.api.extension.ExtendWith;
3935

4036
/**
4137
* Checks the pixels a {@link TabFolder} paints once it has custom colors. The
4238
* shell stays unopened and messages are sent directly, so no window appears and
4339
* no real input is needed.
4440
*/
45-
public class Test_org_eclipse_swt_widgets_TabFolder {
41+
@ExtendWith(PlatformSpecificExecutionExtension.class)
42+
class TabFolderWin32Tests {
4643

4744
private static final int DARK = 0x2F2F2F;
4845
private static final int LIGHT = 0xDCDCDC;
@@ -61,6 +58,7 @@ public void setUp() {
6158
@AfterEach
6259
public void tearDown() {
6360
shell.dispose();
61+
display.dispose();
6462
}
6563

6664
@Test
@@ -224,7 +222,7 @@ public void test_explicitTextDirectionReordersLabel() {
224222

225223
@Test
226224
public void test_autoTextDirectionReordersLabel() {
227-
assertTextDirectionReordersLabel("אבג EEE!", SWT.AUTO_TEXT_DIRECTION);
225+
assertTextDirectionReordersLabel("\u05D0\u05D1\u05D2 EEE!", SWT.AUTO_TEXT_DIRECTION);
228226
}
229227

230228
private void assertTextDirectionReordersLabel(String label, int textDirection) {
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 vogella 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+
* vogella GmbH - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.swt.widgets;
15+
16+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
17+
import static org.junit.jupiter.api.Assertions.assertEquals;
18+
import static org.junit.jupiter.api.Assertions.assertFalse;
19+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
20+
import static org.junit.jupiter.api.Assumptions.assumeTrue;
21+
22+
import java.util.Arrays;
23+
import java.util.Set;
24+
import java.util.TreeSet;
25+
26+
import org.eclipse.swt.SWT;
27+
import org.eclipse.swt.graphics.GC;
28+
import org.eclipse.swt.graphics.Image;
29+
import org.eclipse.swt.graphics.ImageData;
30+
import org.eclipse.swt.graphics.Rectangle;
31+
import org.eclipse.swt.internal.DPIUtil;
32+
import org.eclipse.swt.internal.PlatformSpecificExecutionExtension;
33+
import org.eclipse.swt.internal.win32.OS;
34+
import org.junit.jupiter.api.AfterEach;
35+
import org.junit.jupiter.api.BeforeEach;
36+
import org.junit.jupiter.api.Test;
37+
import org.junit.jupiter.api.extension.ExtendWith;
38+
39+
/** Pins how a multi-column Tree paints a row hovered through native messages, which public API cannot send. */
40+
@ExtendWith(PlatformSpecificExecutionExtension.class)
41+
class TreeWin32Tests {
42+
43+
private static final int COLUMNS = 3;
44+
private static final int COLUMN_WIDTH = 120;
45+
46+
private Display display;
47+
private Shell shell;
48+
private Button focusTarget;
49+
private Tree paintedTree;
50+
private TreeItem paintedItem;
51+
52+
@BeforeEach
53+
public void setUp() {
54+
display = Display.getDefault();
55+
shell = new Shell(display);
56+
focusTarget = new Button(shell, SWT.PUSH);
57+
focusTarget.setBounds(0, 0, 50, 20);
58+
}
59+
60+
@AfterEach
61+
public void tearDown() {
62+
shell.dispose();
63+
display.dispose();
64+
}
65+
66+
@Test
67+
public void test_hoverIsPainted() {
68+
openPaintedTree(SWT.FULL_SELECTION | SWT.MULTI);
69+
// The real pointer may rest on the row.
70+
OS.SendMessage(paintedTree.handle, OS.WM_MOUSELEAVE, 0, 0);
71+
int[] normal = captureColumn(0);
72+
hover();
73+
assertFalse(Arrays.equals(normal, captureColumn(0)), "the simulated hover did not reach the tree");
74+
}
75+
76+
@Test
77+
public void test_hoveredSelectedRowHasOneBackground() {
78+
openPaintedTree(SWT.FULL_SELECTION | SWT.MULTI);
79+
paintedTree.setSelection(paintedItem);
80+
hover();
81+
assertArrayEquals(captureColumn(0), captureColumn(1));
82+
assertArrayEquals(captureColumn(0), captureColumn(2));
83+
}
84+
85+
@Test
86+
public void test_hoveredSelectedRowReportsHotForAllColumns() {
87+
openPaintedTree(SWT.FULL_SELECTION | SWT.MULTI);
88+
Set<Integer> hotColumns = new TreeSet<>();
89+
paintedTree.addListener(SWT.EraseItem, event -> {
90+
if (event.item == paintedItem && (event.detail & SWT.HOT) != 0) {
91+
hotColumns.add(event.index);
92+
}
93+
});
94+
paintedTree.setSelection(paintedItem);
95+
hover();
96+
paintedTree.redraw();
97+
paintedTree.update();
98+
assertEquals(allColumns(), hotColumns);
99+
assertArrayEquals(captureColumn(0), captureColumn(1));
100+
}
101+
102+
@Test
103+
public void test_clearingHotAppliesToTheWholeRow() {
104+
openPaintedTree(SWT.FULL_SELECTION | SWT.MULTI);
105+
paintedTree.addListener(SWT.EraseItem, event -> event.detail &= ~SWT.HOT);
106+
paintedTree.setSelection(paintedItem);
107+
int[] selected = captureColumn(1);
108+
hover();
109+
assertArrayEquals(selected, captureColumn(0));
110+
assertArrayEquals(selected, captureColumn(1));
111+
}
112+
113+
@Test
114+
public void test_hoveredRowWithEraseItemListenerHasOneBackground() {
115+
openPaintedTree(SWT.FULL_SELECTION | SWT.MULTI);
116+
paintedTree.addListener(SWT.EraseItem, event -> {});
117+
hover();
118+
assertArrayEquals(captureColumn(0), captureColumn(1));
119+
assertArrayEquals(captureColumn(0), captureColumn(2));
120+
}
121+
122+
@Test
123+
public void test_unfocusedSelectedRowHasOneBackground() {
124+
openPaintedTree(SWT.FULL_SELECTION | SWT.MULTI);
125+
paintedTree.setSelection(paintedItem);
126+
focusTarget.setFocus();
127+
assertNotEquals(paintedTree.handle, OS.GetFocus(), "the tree kept the focus");
128+
assertArrayEquals(captureColumn(0), captureColumn(1));
129+
hover();
130+
assertArrayEquals(captureColumn(0), captureColumn(1));
131+
}
132+
133+
@Test
134+
public void test_reorderedColumnsHaveOneBackground() {
135+
openPaintedTree(SWT.FULL_SELECTION | SWT.MULTI);
136+
paintedTree.addListener(SWT.EraseItem, event -> {});
137+
paintedTree.setColumnOrder(new int[] { 2, 0, 1 });
138+
paintedTree.setSelection(paintedItem);
139+
hover();
140+
int[] rowEnd = capture(paintedTree.getClientArea().width - 5);
141+
assertArrayEquals(rowEnd, captureColumn(0));
142+
assertArrayEquals(rowEnd, captureColumn(1));
143+
assertArrayEquals(rowEnd, captureColumn(2));
144+
}
145+
146+
@Test
147+
public void test_eraseItemListenerKeepsHoveredSelectionWithoutFullSelection() {
148+
openPaintedTree(SWT.MULTI);
149+
// At 125% the selection edges of the two paint paths differ by a pixel.
150+
assumeTrue(DPIUtil.getZoomForAutoscaleProperty(paintedTree.getMonitor().getZoom()) == 100);
151+
paintedItem.setText("text");
152+
int[] unselected = captureRowTop();
153+
paintedTree.setSelection(paintedItem);
154+
hover();
155+
int[] painted = captureRowTop();
156+
assertFalse(Arrays.equals(unselected, painted), "the selection was not painted");
157+
paintedTree.addListener(SWT.EraseItem, event -> {});
158+
assertArrayEquals(painted, captureRowTop());
159+
}
160+
161+
private static Set<Integer> allColumns() {
162+
Set<Integer> columns = new TreeSet<>();
163+
for (int i = 0; i < COLUMNS; i++) {
164+
columns.add(i);
165+
}
166+
return columns;
167+
}
168+
169+
/** Opens a focused tree without an EraseItem listener, because adding one changes how the tree paints. */
170+
private void openPaintedTree(int style) {
171+
paintedTree = new Tree(shell, style);
172+
paintedTree.setBounds(0, 30, 4 * COLUMN_WIDTH, 150);
173+
for (int i = 0; i < COLUMNS; i++) {
174+
new TreeColumn(paintedTree, SWT.NONE).setWidth(COLUMN_WIDTH);
175+
}
176+
for (int i = 0; i < 3; i++) {
177+
new TreeItem(paintedTree, SWT.NONE);
178+
}
179+
paintedItem = paintedTree.getItem(1);
180+
shell.setSize(5 * COLUMN_WIDTH, 250);
181+
shell.open();
182+
paintedTree.setFocus();
183+
while (display.readAndDispatch()) {
184+
// process all pending events
185+
}
186+
}
187+
188+
/** Hovers the item with a message, so that the real mouse pointer is not involved. */
189+
private void hover() {
190+
Rectangle bounds = paintedItem.getBounds(0);
191+
int zoom = DPIUtil.getZoomForAutoscaleProperty(paintedTree.getMonitor().getZoom());
192+
int x = DPIUtil.pointToPixel(bounds.x + bounds.width / 2, zoom);
193+
int y = DPIUtil.pointToPixel(bounds.y + bounds.height / 2, zoom);
194+
OS.SendMessage(paintedTree.handle, OS.WM_MOUSEMOVE, 0, OS.MAKELPARAM(x, y));
195+
}
196+
197+
/** Returns the pixels of the hovered item's row, from top to bottom, in the middle of a column. */
198+
private int[] captureColumn(int column) {
199+
return capture(COLUMN_WIDTH * column + COLUMN_WIDTH - 10);
200+
}
201+
202+
private int[] capture(int x) {
203+
ImageData data = captureTree();
204+
Rectangle bounds = paintedItem.getBounds(0);
205+
int[] pixels = new int[bounds.height];
206+
for (int i = 0; i < pixels.length; i++) {
207+
pixels[i] = data.getPixel(x, bounds.y + i);
208+
}
209+
return pixels;
210+
}
211+
212+
/** Returns the pixels across the whole row, just above the text. */
213+
private int[] captureRowTop() {
214+
ImageData data = captureTree();
215+
int[] pixels = new int[data.width];
216+
for (int i = 0; i < pixels.length; i++) {
217+
pixels[i] = data.getPixel(i, paintedItem.getBounds(0).y + 1);
218+
}
219+
return pixels;
220+
}
221+
222+
private ImageData captureTree() {
223+
// No events are dispatched here: the WM_MOUSELEAVE of the simulated hover would end it.
224+
paintedTree.redraw();
225+
paintedTree.update();
226+
Rectangle clientArea = paintedTree.getClientArea();
227+
Image image = new Image(display, clientArea.width, clientArea.height);
228+
try {
229+
GC gc = new GC(paintedTree);
230+
try {
231+
gc.copyArea(image, 0, 0);
232+
} finally {
233+
gc.dispose();
234+
}
235+
return image.getImageData();
236+
} finally {
237+
image.dispose();
238+
}
239+
}
240+
}

‎tests/org.eclipse.swt.tests.win32/JUnit Tests/org/eclipse/swt/tests/win32/AllWin32Tests.java‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import org.eclipse.swt.graphics.ImageWin32Tests;
1818
import org.eclipse.swt.tests.win32.widgets.TestTreeColumn;
1919
import org.eclipse.swt.tests.win32.widgets.Test_org_eclipse_swt_widgets_Display;
20-
import org.eclipse.swt.tests.win32.widgets.Test_org_eclipse_swt_widgets_TabFolder;
2120
import org.eclipse.swt.tests.win32.widgets.Test_org_eclipse_swt_widgets_Tree;
2221
import org.junit.platform.suite.api.SelectClasses;
2322
import org.junit.platform.suite.api.Suite;
@@ -27,7 +26,6 @@
2726
Test_org_eclipse_swt_dnd_DND.class, //
2827
Test_org_eclipse_swt_events_KeyEvent.class, //
2928
Test_org_eclipse_swt_widgets_Display.class, //
30-
Test_org_eclipse_swt_widgets_TabFolder.class, //
3129
Test_org_eclipse_swt_widgets_Tree.class, //
3230
TestTreeColumn.class, //
3331
Win32DPIUtilTests.class })

0 commit comments

Comments
 (0)