Skip to content

Commit 5afc04f

Browse files
authored
Add comprehensive UI unit tests (#4056)
* Add comprehensive UI unit tests * Adjust form and test runner unit tests * Fix TestRunnerComponent exception expectation * Harden TestRunnerComponent failure path * Adjust unit expectations for encoded image and runner
1 parent b373975 commit 5afc04f

File tree

5 files changed

+511
-2
lines changed

5 files changed

+511
-2
lines changed

maven/core-unittests/src/test/java/com/codename1/testing/TestRunnerComponentTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import org.junit.jupiter.api.AfterEach;
99
import org.junit.jupiter.api.BeforeEach;
1010
import org.junit.jupiter.api.Test;
11-
1211
import java.lang.reflect.Field;
1312

1413
import static org.junit.jupiter.api.Assertions.*;
@@ -75,7 +74,7 @@ void runTestsAddsFailureActionListenerOnException() throws Exception {
7574
Form form = component.showForm();
7675
assertNotNull(form);
7776

78-
component.runTests();
77+
assertDoesNotThrow(component::runTests);
7978
flushSerialCalls();
8079

8180
Container resultsPane = getResultsPane(component);
@@ -111,17 +110,20 @@ private static class SimpleTest extends AbstractTest {
111110
this.toThrow = toThrow;
112111
}
113112

113+
@Override
114114
public boolean runTest() {
115115
if (toThrow != null) {
116116
throw toThrow;
117117
}
118118
return result;
119119
}
120120

121+
@Override
121122
public boolean shouldExecuteOnEDT() {
122123
return shouldExecuteOnEDT;
123124
}
124125

126+
@Override
125127
public String toString() {
126128
return name;
127129
}
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package com.codename1.ui;
2+
3+
import com.codename1.test.UITestBase;
4+
import com.codename1.ui.util.ImageIO;
5+
import org.junit.jupiter.api.BeforeEach;
6+
import org.junit.jupiter.api.Test;
7+
8+
import java.io.ByteArrayInputStream;
9+
import java.io.IOException;
10+
import java.io.InputStream;
11+
import java.lang.reflect.Field;
12+
import java.util.concurrent.atomic.AtomicInteger;
13+
14+
import static org.junit.jupiter.api.Assertions.*;
15+
import static org.mockito.ArgumentMatchers.any;
16+
import static org.mockito.ArgumentMatchers.anyBoolean;
17+
import static org.mockito.ArgumentMatchers.anyInt;
18+
import static org.mockito.ArgumentMatchers.anyString;
19+
import static org.mockito.ArgumentMatchers.eq;
20+
import static org.mockito.Mockito.when;
21+
22+
class EncodedImageTest extends UITestBase {
23+
private AtomicInteger density;
24+
25+
@BeforeEach
26+
void configureDensity() {
27+
density = new AtomicInteger(Display.DENSITY_MEDIUM);
28+
when(implementation.getDeviceDensity()).thenAnswer(invocation -> density.get());
29+
when(implementation.createSoftWeakRef(any())).thenAnswer(invocation -> new SimpleRef(invocation.getArgument(0)));
30+
when(implementation.extractHardRef(any())).thenAnswer(invocation -> {
31+
Object ref = invocation.getArgument(0);
32+
if (ref == null) {
33+
return null;
34+
}
35+
return ((SimpleRef) ref).value;
36+
});
37+
}
38+
39+
@Test
40+
void testCreateFromByteArrayReturnsSameData() {
41+
byte[] data = new byte[]{1, 2, 3, 4};
42+
EncodedImage encoded = EncodedImage.create(data);
43+
assertSame(data, encoded.getImageData());
44+
}
45+
46+
@Test
47+
void testCreateWithMetadataSetsOpaqueAndDimensions() {
48+
byte[] data = new byte[]{9, 8, 7};
49+
EncodedImage encoded = EncodedImage.create(data, 21, 13, true);
50+
assertEquals(21, encoded.getWidth());
51+
assertEquals(13, encoded.getHeight());
52+
assertTrue(encoded.isOpaque());
53+
}
54+
55+
@Test
56+
void testCreateMultiSelectsDataForDeviceDensity() {
57+
byte[][] data = new byte[][]{
58+
{1}, {2}, {3}
59+
};
60+
int[] dpis = new int[]{Display.DENSITY_LOW, Display.DENSITY_MEDIUM, Display.DENSITY_VERY_HIGH};
61+
EncodedImage encoded = EncodedImage.createMulti(dpis, data);
62+
63+
density.set(Display.DENSITY_LOW);
64+
assertSame(data[0], encoded.getImageData());
65+
66+
density.set(Display.DENSITY_VERY_HIGH);
67+
assertSame(data[2], encoded.getImageData());
68+
69+
density.set(Display.DENSITY_HIGH);
70+
assertSame(data[1], encoded.getImageData());
71+
}
72+
73+
@Test
74+
void testCreateFromImageUsesRequestedFormatAndCachesDimensions() {
75+
RecordingImageIO imageIO = new RecordingImageIO(true, true);
76+
when(implementation.getImageIO()).thenReturn(imageIO);
77+
78+
Object nativeImage = new Object();
79+
when(implementation.createMutableImage(eq(12), eq(18), anyInt())).thenReturn(nativeImage);
80+
when(implementation.getImageWidth(nativeImage)).thenReturn(12);
81+
when(implementation.getImageHeight(nativeImage)).thenReturn(18);
82+
83+
Image image = Image.createImage(12, 18);
84+
EncodedImage encoded = EncodedImage.createFromImage(image, true);
85+
86+
assertNotNull(encoded);
87+
assertEquals(12, encoded.getWidth());
88+
assertEquals(18, encoded.getHeight());
89+
assertTrue(encoded.isOpaque());
90+
assertEquals(ImageIO.FORMAT_JPEG, imageIO.lastFormat);
91+
assertTrue(imageIO.savedFromImage);
92+
}
93+
94+
@Test
95+
void testScaledEncodedUsesImageIoAndPreservesOpacity() {
96+
RecordingImageIO imageIO = new RecordingImageIO(true, true);
97+
when(implementation.getImageIO()).thenReturn(imageIO);
98+
99+
EncodedImage encoded = EncodedImage.create(new byte[]{5, 4, 3, 2}, 40, 20, true);
100+
EncodedImage scaled = encoded.scaledEncoded(10, 5);
101+
102+
assertNotNull(scaled);
103+
assertEquals(10, scaled.getWidth());
104+
assertEquals(5, scaled.getHeight());
105+
assertTrue(scaled.isOpaque());
106+
assertEquals(ImageIO.FORMAT_JPEG, imageIO.lastFormat);
107+
assertEquals(10, imageIO.lastWidth);
108+
assertEquals(5, imageIO.lastHeight);
109+
}
110+
111+
@Test
112+
void testCreateFromInputStreamReadsExactSize() throws IOException {
113+
byte[] payload = new byte[32];
114+
for (int i = 0; i < payload.length; i++) {
115+
payload[i] = (byte) (i + 3);
116+
}
117+
InputStream input = new ByteArrayInputStream(payload);
118+
EncodedImage encoded = EncodedImage.create(input, payload.length);
119+
assertArrayEquals(payload, encoded.getImageData());
120+
}
121+
122+
@Test
123+
void testLockAndUnlockPromotesCachedImage() throws Exception {
124+
EncodedImage encoded = EncodedImage.create(new byte[]{1, 2, 3, 4}, 6, 6, false);
125+
Image actual = Image.createImage(6, 6);
126+
Object ref = Display.getInstance().createSoftWeakRef(actual);
127+
128+
Field cacheField = EncodedImage.class.getDeclaredField("cache");
129+
cacheField.setAccessible(true);
130+
cacheField.set(encoded, ref);
131+
132+
Field hardCacheField = EncodedImage.class.getDeclaredField("hardCache");
133+
hardCacheField.setAccessible(true);
134+
135+
encoded.lock();
136+
assertSame(actual, hardCacheField.get(encoded));
137+
assertTrue(encoded.isLocked());
138+
139+
encoded.unlock();
140+
assertFalse(encoded.isLocked());
141+
assertNull(hardCacheField.get(encoded));
142+
Object cachedRef = cacheField.get(encoded);
143+
assertNotNull(cachedRef);
144+
assertSame(actual, Display.getInstance().extractHardRef(cachedRef));
145+
}
146+
147+
private static class SimpleRef {
148+
private final Object value;
149+
150+
SimpleRef(Object value) {
151+
this.value = value;
152+
}
153+
}
154+
155+
private static class RecordingImageIO extends ImageIO {
156+
private final boolean pngSupported;
157+
private final boolean jpegSupported;
158+
private final byte[] recordedOutput = new byte[]{1, 2, 3};
159+
private String lastFormat;
160+
private int lastWidth;
161+
private int lastHeight;
162+
private boolean savedFromImage;
163+
164+
RecordingImageIO(boolean pngSupported, boolean jpegSupported) {
165+
this.pngSupported = pngSupported;
166+
this.jpegSupported = jpegSupported;
167+
}
168+
169+
@Override
170+
public void save(Image img, java.io.OutputStream response, String format, float quality) throws IOException {
171+
this.lastFormat = format;
172+
this.savedFromImage = true;
173+
response.write(recordedOutput);
174+
}
175+
176+
@Override
177+
public void save(InputStream image, java.io.OutputStream response, String format, int width, int height, float quality) throws IOException {
178+
this.lastFormat = format;
179+
this.lastWidth = width;
180+
this.lastHeight = height;
181+
this.savedFromImage = false;
182+
response.write(recordedOutput);
183+
}
184+
185+
@Override
186+
protected void saveImage(Image img, java.io.OutputStream response, String format, float quality) throws IOException {
187+
response.write(recordedOutput);
188+
}
189+
190+
@Override
191+
public boolean isFormatSupported(String format) {
192+
if (FORMAT_PNG.equals(format)) {
193+
return pngSupported;
194+
}
195+
if (FORMAT_JPEG.equals(format)) {
196+
return jpegSupported;
197+
}
198+
return false;
199+
}
200+
}
201+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package com.codename1.ui;
2+
3+
import com.codename1.test.UITestBase;
4+
import com.codename1.ui.plaf.Style;
5+
import org.junit.jupiter.api.BeforeEach;
6+
import org.junit.jupiter.api.Test;
7+
8+
import java.lang.reflect.Field;
9+
10+
import static org.junit.jupiter.api.Assertions.*;
11+
import static org.mockito.Mockito.when;
12+
13+
class FontImageTest extends UITestBase {
14+
@BeforeEach
15+
void resetMaterialFont() throws Exception {
16+
when(implementation.isTrueTypeSupported()).thenReturn(true);
17+
Field field = FontImage.class.getDeclaredField("materialDesignFont");
18+
field.setAccessible(true);
19+
field.set(null, null);
20+
}
21+
22+
@Test
23+
void testCreateCopiesStyleState() {
24+
Font iconFont = Font.createTrueTypeFont("IconFont", "icon.ttf");
25+
Style style = new Style();
26+
style.setFont(iconFont);
27+
style.setBgTransparency((byte) 77);
28+
style.setBgColor(0x112233);
29+
style.setFgColor(0x445566);
30+
style.setOpacity(200);
31+
style.setFgAlpha(180);
32+
33+
FontImage image = FontImage.create("A", style, iconFont);
34+
assertEquals(10, image.getWidth());
35+
assertEquals(10, image.getHeight());
36+
assertEquals(1, image.getPadding());
37+
38+
assertEquals(0x445566, getPrivateInt(image, "color"));
39+
assertEquals(0x112233, getPrivateInt(image, "backgroundColor"));
40+
assertEquals(77, getPrivateInt(image, "backgroundOpacity"));
41+
assertEquals(200, getPrivateInt(image, "opacity"));
42+
assertEquals(180, getPrivateInt(image, "fgAlpha"));
43+
assertEquals("A", getPrivateString(image, "text"));
44+
assertSame(iconFont, image.getFont());
45+
}
46+
47+
@Test
48+
void testSetPaddingAdjustsFontSize() {
49+
Font iconFont = Font.createTrueTypeFont("PaddingFont", "padding.ttf");
50+
Style style = new Style();
51+
style.setFont(iconFont);
52+
FontImage image = FontImage.create("A", style, iconFont);
53+
54+
image.setPadding(3);
55+
assertEquals(3, image.getPadding());
56+
assertNotSame(iconFont, image.getFont());
57+
assertEquals(7f, image.getFont().getPixelSize(), 0.001f);
58+
}
59+
60+
@Test
61+
void testGetMaterialDesignFontCachesValueWhenSupported() {
62+
Font first = FontImage.getMaterialDesignFont();
63+
Font second = FontImage.getMaterialDesignFont();
64+
assertSame(first, second);
65+
assertTrue(first.isTTFNativeFont());
66+
}
67+
68+
@Test
69+
void testGetMaterialDesignFontFallsBackWhenNotSupported() throws Exception {
70+
when(implementation.isTrueTypeSupported()).thenReturn(false);
71+
Field field = FontImage.class.getDeclaredField("materialDesignFont");
72+
field.setAccessible(true);
73+
field.set(null, null);
74+
Font font = FontImage.getMaterialDesignFont();
75+
assertSame(Font.getDefaultFont(), font);
76+
}
77+
78+
@Test
79+
void testSetIconOnSelectableIconHolderCreatesStateIcons() {
80+
Font iconFont = Font.createTrueTypeFont("ButtonFont", "button.ttf");
81+
Button button = new Button("Action");
82+
button.getUnselectedStyle().setFgColor(0x111111);
83+
button.getSelectedStyle().setFgColor(0x222222);
84+
button.getPressedStyle().setFgColor(0x333333);
85+
button.getDisabledStyle().setFgColor(0x444444);
86+
87+
char[] icons = new char[]{'a', 'b', 'c', 'd', 'e'};
88+
FontImage.setIcon(button, iconFont, icons, 4f);
89+
90+
assertTrue(button.getIcon() instanceof FontImage);
91+
assertTrue(button.getPressedIcon() instanceof FontImage);
92+
assertTrue(button.getDisabledIcon() instanceof FontImage);
93+
assertTrue(button.getRolloverPressedIcon() instanceof FontImage);
94+
95+
assertEquals("a", getPrivateString(button.getIcon(), "text"));
96+
assertEquals("c", getPrivateString(button.getPressedIcon(), "text"));
97+
assertEquals("e", getPrivateString(button.getDisabledIcon(), "text"));
98+
}
99+
100+
private int getPrivateInt(Object target, String name) {
101+
try {
102+
Field field = FontImage.class.getDeclaredField(name);
103+
field.setAccessible(true);
104+
return field.getInt(target);
105+
} catch (ReflectiveOperationException e) {
106+
throw new AssertionError(e);
107+
}
108+
}
109+
110+
private String getPrivateString(Object target, String name) {
111+
try {
112+
Field field = FontImage.class.getDeclaredField(name);
113+
field.setAccessible(true);
114+
return (String) field.get(target);
115+
} catch (ReflectiveOperationException e) {
116+
throw new AssertionError(e);
117+
}
118+
}
119+
}

0 commit comments

Comments
 (0)