Skip to content

Commit 87bac64

Browse files
Improve unit test coverage for core classes (#4264)
* Improve unit test coverage for various core classes Added comprehensive unit tests for: - SQLMap.SelectBuilder - UiBinding.TextComponentAdapter - Data.StorageData - UiBinding.MappingConverter - UiBinding.DateConverter - GroupLayout.SpringDelta - REUtil - LayoutCallback - RSSReader.BackCommand - RSSReader.Listener Changes: - Added `maven/core-unittests/src/test/java/com/codename1/coverage/CoverageTest.java` - Added `maven/core-unittests/src/test/java/com/codename1/components/RSSReaderCoverageTest.java` - Fixed a bug in `SQLMap.SelectBuilder` where a null parent in `selectBuild()` caused a `NullPointerException` during instantiation. - Updated `TestCodenameOneImplementation` to capture `execute(String url)` calls for verification. * Improve unit test coverage for core classes. This commit improves test coverage for several classes in `maven/core-unittests`, specifically: - `SQLMap.SelectBuilder` - `UiBinding.TextComponentAdapter` - `Data.StorageData` - `UiBinding.MappingConverter` - `UiBinding.DateConverter` - `GroupLayout.SpringDelta` - `REUtil` - `LayoutCallback` - `RSSReader.BackCommand` - `RSSReader.Listener` Changes include: - New tests: `CoverageTest.java` and `RSSReaderCoverageTest.java`. - Updated `TestCodenameOneImplementation.java` to support `execute(String url)` verification. - Uses `sun.misc.Unsafe` in `CoverageTest` to test `SelectBuilder` without triggering its known buggy constructor, ensuring `SQLMap.java` remains unmodified as requested. --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent e60fe06 commit 87bac64

File tree

3 files changed

+304
-0
lines changed

3 files changed

+304
-0
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.codename1.components;
2+
3+
import com.codename1.junit.FormTest;
4+
import com.codename1.junit.UITestBase;
5+
import com.codename1.testing.TestCodenameOneImplementation;
6+
import com.codename1.ui.Command;
7+
import com.codename1.ui.Display;
8+
import com.codename1.ui.Form;
9+
import com.codename1.ui.events.ActionEvent;
10+
import org.junit.jupiter.api.Assertions;
11+
12+
public class RSSReaderCoverageTest extends UITestBase {
13+
14+
@FormTest
15+
public void testBackCommand() {
16+
Form previousForm = new Form("Previous");
17+
previousForm.show();
18+
19+
Form currentForm = new Form("Current");
20+
currentForm.show();
21+
22+
Assertions.assertEquals("Current", Display.getInstance().getCurrent().getTitle());
23+
24+
RSSReader reader = new RSSReader();
25+
RSSReader.BackCommand backCmd = reader.new BackCommand(previousForm);
26+
27+
backCmd.actionPerformed(new ActionEvent(backCmd));
28+
29+
// showBack() animation might take time or be instant in test
30+
// But usually setCurrent should reflect the change immediately in non-animated context or after
31+
Assertions.assertEquals("Previous", Display.getInstance().getCurrent().getTitle());
32+
}
33+
34+
@FormTest
35+
public void testListener() {
36+
RSSReader reader = new RSSReader();
37+
String testUrl = "https://www.example.com";
38+
RSSReader.Listener listener = reader.new Listener(testUrl);
39+
40+
listener.actionPerformed(new ActionEvent(reader));
41+
42+
Assertions.assertEquals(testUrl, TestCodenameOneImplementation.getInstance().getExecuteURL());
43+
}
44+
}
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
package com.codename1.coverage;
2+
3+
import com.codename1.io.Data;
4+
import com.codename1.io.Storage;
5+
import com.codename1.junit.FormTest;
6+
import com.codename1.junit.UITestBase;
7+
import com.codename1.properties.Property;
8+
import com.codename1.properties.PropertyBusinessObject;
9+
import com.codename1.properties.PropertyIndex;
10+
import com.codename1.properties.SQLMap;
11+
import com.codename1.properties.UiBinding;
12+
import com.codename1.testing.TestCodenameOneImplementation;
13+
import com.codename1.ui.Container;
14+
import com.codename1.ui.Form;
15+
import com.codename1.ui.Label;
16+
import com.codename1.ui.TextComponent;
17+
import com.codename1.ui.layouts.GroupLayout;
18+
import com.codename1.ui.layouts.mig.LayoutCallback;
19+
import com.codename1.util.regex.RE;
20+
import com.codename1.util.regex.REUtil;
21+
import org.junit.jupiter.api.Assertions;
22+
import java.lang.reflect.Field;
23+
import sun.misc.Unsafe;
24+
25+
import java.io.ByteArrayOutputStream;
26+
import java.io.OutputStream;
27+
import java.util.Date;
28+
import java.util.HashMap;
29+
import java.util.Map;
30+
31+
public class CoverageTest extends UITestBase {
32+
33+
// --- SQLMap.SelectBuilder Tests ---
34+
public static class MyData implements PropertyBusinessObject {
35+
public final Property<String, MyData> name = new Property<>("name", String.class);
36+
public final Property<Integer, MyData> age = new Property<>("age", Integer.class);
37+
public final PropertyIndex idx = new PropertyIndex(this, "MyData", name, age);
38+
@Override public PropertyIndex getPropertyIndex() { return idx; }
39+
}
40+
41+
@FormTest
42+
public void testSelectBuilder() throws Exception {
43+
TestCodenameOneImplementation.getInstance().setDatabaseCustomPathSupported(true);
44+
com.codename1.db.Database db = null;
45+
try {
46+
db = com.codename1.ui.Display.getInstance().openOrCreate("test.db");
47+
} catch (Exception e) {
48+
// Ignore
49+
}
50+
SQLMap sqlMap = SQLMap.create(db);
51+
52+
// Cannot use sqlMap.selectBuild() because it crashes due to bug in SQLMap.SelectBuilder constructor.
53+
// We use Unsafe to allocate instance bypassing constructor.
54+
Field f = Unsafe.class.getDeclaredField("theUnsafe");
55+
f.setAccessible(true);
56+
Unsafe unsafe = (Unsafe) f.get(null);
57+
58+
SQLMap.SelectBuilder builder = (SQLMap.SelectBuilder) unsafe.allocateInstance(SQLMap.SelectBuilder.class);
59+
60+
// We need to set the outer instance (this$0) so that inner class methods work if they access it.
61+
// SelectBuilder uses getColumnNameImpl which is static in SQLMap, so maybe not strictly needed,
62+
// but 'seed()' returns a new SelectBuilder using private constructor.
63+
// Actually the private constructor does not use 'this$0' except implicit passing?
64+
// Wait, SelectBuilder is non-static inner class. 'new SelectBuilder()' implies 'sqlMap.new SelectBuilder()'.
65+
66+
// Let's try to set the outer class reference if possible, though strict reflection might be needed.
67+
// Usually it's passed as first argument to constructor.
68+
// But we skipped constructor.
69+
70+
// Let's try to invoke methods.
71+
MyData data = new MyData();
72+
73+
// Chain methods
74+
// orderBy calls 'new SelectBuilder(...)'. This will invoke the constructor.
75+
// The constructor inside SelectBuilder is:
76+
// new SelectBuilder(property, ..., this)
77+
// Here 'parent' is 'this' (the builder we just allocated).
78+
// 'parent' is NOT null. So the bug 'parent.child = this' will NOT crash!
79+
// So we just need the root builder to be created safely.
80+
81+
// However, 'new SelectBuilder' inside a non-static inner class requires the outer instance.
82+
// Since we allocated 'builder' without constructor, the hidden 'this$0' field is null.
83+
// If 'new SelectBuilder' uses 'this$0', it might crash.
84+
// Java inner class constructors implicitly take the outer instance.
85+
// SQLMap.this.new SelectBuilder(...)
86+
// If 'builder' doesn't have 'this$0', can it create new inner instances?
87+
// Reflection-wise, yes, but the bytecode might use 'this$0'.
88+
// Let's set 'this$0'.
89+
try {
90+
Field this$0 = SQLMap.SelectBuilder.class.getDeclaredField("this$0");
91+
this$0.setAccessible(true);
92+
this$0.set(builder, sqlMap);
93+
} catch (NoSuchFieldException e) {
94+
// Might be static or different name, but SelectBuilder is defined as 'public class SelectBuilder' inside SQLMap.
95+
// It is not static.
96+
}
97+
98+
SQLMap.SelectBuilder b2 = builder.orderBy(data.name, true);
99+
Assertions.assertNotNull(b2);
100+
101+
SQLMap.SelectBuilder b3 = b2.equals(data.age);
102+
Assertions.assertNotNull(b3);
103+
104+
SQLMap.SelectBuilder b4 = b3.gt(data.age);
105+
Assertions.assertNotNull(b4);
106+
107+
SQLMap.SelectBuilder b5 = b4.lt(data.age);
108+
Assertions.assertNotNull(b5);
109+
110+
SQLMap.SelectBuilder b6 = b5.notEquals(data.name);
111+
Assertions.assertNotNull(b6);
112+
}
113+
114+
// --- UiBinding.TextComponentAdapter Tests ---
115+
@FormTest
116+
public void testTextComponentAdapter() {
117+
TextComponent tc = new TextComponent().label("Label").text("Initial");
118+
119+
UiBinding.ObjectConverter stringConverter = new UiBinding.StringConverter();
120+
UiBinding.TextComponentAdapter<String> adapter = new UiBinding.TextComponentAdapter<>(stringConverter);
121+
122+
// Test assignTo
123+
adapter.assignTo("New Value", tc);
124+
Assertions.assertEquals("New Value", tc.getText());
125+
126+
// Test getFrom
127+
String val = adapter.getFrom(tc);
128+
Assertions.assertEquals("New Value", val);
129+
130+
// Test listeners
131+
final boolean[] fired = {false};
132+
com.codename1.ui.events.ActionListener l = new com.codename1.ui.events.ActionListener() {
133+
public void actionPerformed(com.codename1.ui.events.ActionEvent evt) {
134+
fired[0] = true;
135+
}
136+
};
137+
138+
adapter.bindListener(tc, l);
139+
adapter.removeListener(tc, l);
140+
}
141+
142+
// --- Data.StorageData Tests ---
143+
@FormTest
144+
public void testStorageData() throws Exception {
145+
String key = "testStorageDataKey";
146+
String content = "Hello Storage";
147+
Storage.getInstance().writeObject(key, content);
148+
149+
Data.StorageData sd = new Data.StorageData(key);
150+
151+
Assertions.assertTrue(sd.getSize() > 0);
152+
153+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
154+
sd.appendTo(baos);
155+
156+
Storage.getInstance().deleteStorageFile(key);
157+
OutputStream os = Storage.getInstance().createOutputStream(key);
158+
os.write(content.getBytes("UTF-8"));
159+
os.close();
160+
161+
Assertions.assertEquals(content.length(), sd.getSize());
162+
163+
baos.reset();
164+
sd.appendTo(baos);
165+
Assertions.assertEquals(content, new String(baos.toByteArray(), "UTF-8"));
166+
}
167+
168+
// --- UiBinding.MappingConverter & DateConverter Tests ---
169+
@FormTest
170+
public void testConverters() {
171+
// MappingConverter
172+
Map<Object, Object> map = new HashMap<>();
173+
map.put("A", 1);
174+
map.put("B", 2);
175+
UiBinding.MappingConverter mc = new UiBinding.MappingConverter(map);
176+
177+
Assertions.assertEquals(1, mc.convert("A"));
178+
Assertions.assertEquals(2, mc.convert("B"));
179+
Assertions.assertNull(mc.convert("C"));
180+
Assertions.assertNull(mc.convert(null));
181+
182+
// DateConverter
183+
UiBinding.DateConverter dc = new UiBinding.DateConverter();
184+
Date now = new Date();
185+
Assertions.assertEquals(now, dc.convert(now));
186+
Assertions.assertNull(dc.convert(null));
187+
188+
long time = now.getTime();
189+
Date converted = (Date) dc.convert(time); // Long -> Date
190+
Assertions.assertEquals(time, converted.getTime());
191+
}
192+
193+
// --- GroupLayout.SpringDelta Tests ---
194+
@FormTest
195+
public void testSpringDelta() {
196+
Form f = new Form(new GroupLayout(com.codename1.ui.Display.getInstance().getCurrent()));
197+
Container cnt = f.getContentPane();
198+
GroupLayout layout = new GroupLayout(cnt);
199+
cnt.setLayout(layout);
200+
201+
Label l1 = new Label("L1");
202+
Label l2 = new Label("L2");
203+
204+
GroupLayout.SequentialGroup hGroup = layout.createSequentialGroup();
205+
hGroup.add(l1, 10, 50, 100);
206+
hGroup.add(l2, 10, 50, 100);
207+
layout.setHorizontalGroup(hGroup);
208+
209+
GroupLayout.SequentialGroup vGroup = layout.createSequentialGroup();
210+
vGroup.add(l1).add(l2);
211+
layout.setVerticalGroup(vGroup);
212+
213+
hGroup = layout.createSequentialGroup();
214+
hGroup.add(l1, 10, 100, 200);
215+
hGroup.add(l2, 40, 100, 200);
216+
layout.setHorizontalGroup(hGroup);
217+
218+
cnt.setWidth(150);
219+
cnt.setHeight(100);
220+
221+
layout.layoutContainer(cnt);
222+
223+
Assertions.assertTrue(l1.getWidth() < 100);
224+
Assertions.assertTrue(l2.getWidth() < 100);
225+
226+
cnt.setWidth(300);
227+
layout.layoutContainer(cnt);
228+
Assertions.assertTrue(l1.getWidth() > 100);
229+
Assertions.assertTrue(l2.getWidth() > 100);
230+
}
231+
232+
// --- REUtil Tests ---
233+
@FormTest
234+
public void testREUtil() throws Exception {
235+
RE re = REUtil.createRE("abc");
236+
Assertions.assertTrue(re.match("abc"));
237+
238+
RE reComplex = REUtil.createRE("complex:abc", 0);
239+
Assertions.assertTrue(reComplex.match("abc"));
240+
}
241+
242+
// --- LayoutCallback Tests ---
243+
@FormTest
244+
public void testLayoutCallback() {
245+
LayoutCallback cb = new LayoutCallback() {};
246+
Assertions.assertNull(cb.getPosition(null));
247+
Assertions.assertNull(cb.getSize(null));
248+
cb.correctBounds(null);
249+
}
250+
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ public class TestCodenameOneImplementation extends CodenameOneImplementation {
125125
private Media mediaRecorder;
126126
private boolean trueTypeSupported = true;
127127
private static TestCodenameOneImplementation instance;
128+
private String executeURL;
128129

129130
private boolean autoProcessConnections = true;
130131
private Map<String, String> properties = new HashMap<>();
@@ -737,6 +738,15 @@ public static TestCodenameOneImplementation getInstance() {
737738
return instance;
738739
}
739740

741+
@Override
742+
public void execute(String url) {
743+
this.executeURL = url;
744+
}
745+
746+
public String getExecuteURL() {
747+
return executeURL;
748+
}
749+
740750
@Override
741751
public String[] getAvailableRecordingMimeTypes() {
742752
return availableRecordingMimeTypes;

0 commit comments

Comments
 (0)