Skip to content

Commit c74906f

Browse files
authored
Add comprehensive tests for JavaScript context and date formatting (#3998)
1 parent c46cf4e commit c74906f

File tree

3 files changed

+624
-0
lines changed

3 files changed

+624
-0
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
package com.codename1.javascript;
2+
3+
import com.codename1.test.UITestBase;
4+
import com.codename1.ui.BrowserComponent;
5+
import com.codename1.util.Callback;
6+
import com.codename1.util.SuccessCallback;
7+
import java.lang.ref.WeakReference;
8+
import java.util.ArrayDeque;
9+
import java.util.Map;
10+
import java.util.Queue;
11+
import org.junit.jupiter.api.BeforeEach;
12+
import org.junit.jupiter.api.Test;
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.anyString;
18+
import static org.mockito.ArgumentMatchers.eq;
19+
import static org.mockito.Mockito.doAnswer;
20+
import static org.mockito.Mockito.doNothing;
21+
import static org.mockito.Mockito.doReturn;
22+
import static org.mockito.Mockito.mock;
23+
import static org.mockito.Mockito.never;
24+
import static org.mockito.Mockito.spy;
25+
import static org.mockito.Mockito.times;
26+
import static org.mockito.Mockito.verify;
27+
import static org.mockito.Mockito.when;
28+
29+
class JSObjectTest extends UITestBase {
30+
private BrowserComponent browser;
31+
private Queue<String> responses;
32+
33+
private static class RecordingJavascriptContext extends JavascriptContext {
34+
private final Queue<JSObject> recordedFunctions = new ArrayDeque<JSObject>();
35+
private final Queue<JSObject> recordedSelfs = new ArrayDeque<JSObject>();
36+
private final Queue<Object[]> recordedParams = new ArrayDeque<Object[]>();
37+
private final Queue<Callback> recordedCallbacks = new ArrayDeque<Callback>();
38+
39+
RecordingJavascriptContext(BrowserComponent browser) {
40+
super(browser);
41+
}
42+
43+
@Override
44+
public void callAsync(JSObject func, JSObject self, Object[] params, Callback callback) {
45+
recordedFunctions.add(func);
46+
recordedSelfs.add(self);
47+
recordedParams.add(params);
48+
recordedCallbacks.add(callback);
49+
}
50+
51+
Callback removeNextCallback() {
52+
return recordedCallbacks.remove();
53+
}
54+
55+
JSObject removeNextFunction() {
56+
return recordedFunctions.remove();
57+
}
58+
59+
JSObject removeNextSelf() {
60+
return recordedSelfs.remove();
61+
}
62+
63+
Object[] removeNextParams() {
64+
return recordedParams.remove();
65+
}
66+
}
67+
68+
@BeforeEach
69+
void setUpBrowser() {
70+
browser = mock(BrowserComponent.class);
71+
responses = new ArrayDeque<String>();
72+
when(browser.getBrowserNavigationCallback()).thenReturn(null);
73+
when(implementation.createSoftWeakRef(any())).thenAnswer(invocation -> new WeakReference<Object>(invocation.getArgument(0)));
74+
when(implementation.extractHardRef(any())).thenAnswer(invocation -> {
75+
Object ref = invocation.getArgument(0);
76+
if (ref instanceof WeakReference) {
77+
return ((WeakReference) ref).get();
78+
}
79+
return null;
80+
});
81+
doNothing().when(browser).execute(anyString());
82+
when(browser.executeAndReturnString(anyString())).thenAnswer(invocation -> responses.isEmpty() ? "0" : responses.remove());
83+
}
84+
85+
@Test
86+
void constructorStoresObjectIdAndRetains() throws Exception {
87+
responses.add("ignored");
88+
responses.add("object");
89+
responses.add("5");
90+
RecordingJavascriptContext context = createContextSpy();
91+
92+
JSObject object = new JSObject(context, "window");
93+
94+
assertEquals(5, object.objectId);
95+
Map objectMap = getObjectMap(context);
96+
assertTrue(objectMap.containsKey(Integer.valueOf(5)));
97+
}
98+
99+
@Test
100+
void constructorThrowsWhenExpressionIsNotObject() {
101+
responses.add("ignored");
102+
responses.add("string");
103+
RecordingJavascriptContext context = createContextSpy();
104+
105+
assertThrows(JSException.class, () -> new JSObject(context, "1"));
106+
}
107+
108+
@Test
109+
void typedGettersDelegateToContext() {
110+
responses.add("ignored");
111+
responses.add("object");
112+
responses.add("3");
113+
RecordingJavascriptContext context = createContextSpy();
114+
JSObject object = new JSObject(context, "window");
115+
116+
String pointer = object.toJSPointer();
117+
doReturn("name").when(context).get(pointer + ".label");
118+
doReturn(Double.valueOf(10)).when(context).get(pointer + ".count");
119+
doReturn(Boolean.TRUE).when(context).get(pointer + ".flag");
120+
doReturn(Double.valueOf(7)).when(context).get(pointer + "[2]");
121+
doReturn(object).when(context).get(pointer + ".self");
122+
123+
assertEquals("name", object.getString("label"));
124+
assertEquals(10, object.getInt("count"));
125+
assertTrue(object.getBoolean("flag"));
126+
assertEquals(10.0d, object.getDouble("count"), 0.00001d);
127+
assertEquals(object, object.getObject("self"));
128+
assertEquals(7, object.getInt(2));
129+
assertEquals(7.0d, object.getDouble(2), 0.00001d);
130+
}
131+
132+
@Test
133+
void setWithJSFunctionRegistersCallback() {
134+
responses.add("ignored");
135+
responses.add("object");
136+
responses.add("4");
137+
RecordingJavascriptContext context = createContextSpy();
138+
JSObject object = new JSObject(context, "window");
139+
140+
JSFunction function = mock(JSFunction.class);
141+
object.set("handler", function, true);
142+
143+
verify(context).addCallback(eq(object), eq("handler"), eq(function), eq(true));
144+
verify(context, never()).set(anyString(), any());
145+
}
146+
147+
@Test
148+
void setDelegatesToContextForValues() {
149+
responses.add("ignored");
150+
responses.add("object");
151+
responses.add("6");
152+
RecordingJavascriptContext context = createContextSpy();
153+
JSObject object = new JSObject(context, "window");
154+
155+
doNothing().when(context).set(anyString(), any());
156+
doNothing().when(context).set(anyString(), any(), anyBoolean());
157+
158+
object.set("name", "Codename One");
159+
object.set("'0'", Integer.valueOf(2));
160+
object.setBoolean("flag", true);
161+
object.setDouble("ratio", 3.5d);
162+
object.setInt(1, 9);
163+
164+
String pointer = object.toJSPointer();
165+
verify(context).set(eq(pointer + ".name"), eq((Object) "Codename One"));
166+
verify(context).set(eq(pointer + "['0']"), eq((Object) Integer.valueOf(2)));
167+
verify(context).set(eq(pointer + ".flag"), eq((Object) Boolean.TRUE));
168+
verify(context).set(eq(pointer + ".ratio"), eq((Object) Double.valueOf(3.5d)));
169+
verify(context).set(eq(pointer + "[1]"), eq((Object) Integer.valueOf(9)), eq(false));
170+
}
171+
172+
@Test
173+
void callDelegatesToJavascriptContext() {
174+
responses.add("ignored");
175+
responses.add("object");
176+
responses.add("8");
177+
JavascriptContext context = createContextSpy();
178+
JSObject object = new JSObject(context, "window");
179+
180+
doReturn("done").when(context).call(anyString(), eq(object), any(Object[].class));
181+
182+
Object result = object.call("sum", new Object[]{Integer.valueOf(1), Integer.valueOf(2)});
183+
184+
assertEquals("done", result);
185+
verify(context).call(eq(object.toJSPointer() + ".sum"), eq(object), any(Object[].class));
186+
}
187+
188+
@Test
189+
void callAsyncDelegatesToJavascriptContext() {
190+
responses.add("ignored");
191+
responses.add("object");
192+
responses.add("9");
193+
RecordingJavascriptContext context = createContextSpy();
194+
JSObject object = new JSObject(context, "window");
195+
196+
Callback callback = mock(Callback.class);
197+
SuccessCallback successCallback = mock(SuccessCallback.class);
198+
199+
String pointer = object.toJSPointer();
200+
object.callAsync("action", new Object[]{}, callback);
201+
object.callAsync("action", new Object[]{}, successCallback);
202+
203+
verify(context).callAsync(eq(object), eq(object), any(Object[].class), eq(callback));
204+
verify(context, times(2)).callAsync(eq(object), eq(object), any(Object[].class), any(Callback.class));
205+
206+
Callback first = context.removeNextCallback();
207+
assertSame(callback, first);
208+
assertSame(object, context.removeNextFunction());
209+
assertSame(object, context.removeNextSelf());
210+
assertArrayEquals(new Object[]{}, context.removeNextParams());
211+
212+
Callback second = context.removeNextCallback();
213+
assertNotSame(successCallback, second);
214+
second.onSucess("value");
215+
verify(successCallback).onSucess("value");
216+
assertSame(object, context.removeNextFunction());
217+
assertSame(object, context.removeNextSelf());
218+
assertArrayEquals(new Object[]{}, context.removeNextParams());
219+
}
220+
221+
@Test
222+
void removeCallbackDelegatesToContext() {
223+
responses.add("ignored");
224+
responses.add("object");
225+
responses.add("10");
226+
JavascriptContext context = createContextSpy();
227+
JSObject object = new JSObject(context, "window");
228+
229+
doNothing().when(context).removeCallback(eq(object), anyString(), eq(false));
230+
231+
object.removeCallback("listener");
232+
233+
verify(context).removeCallback(eq(object), eq("listener"), eq(false));
234+
}
235+
236+
private RecordingJavascriptContext createContextSpy() {
237+
RecordingJavascriptContext context = new RecordingJavascriptContext(browser);
238+
RecordingJavascriptContext spyContext = spy(context);
239+
doAnswer(invocation -> {
240+
JSObject obj = invocation.getArgument(0);
241+
Map map = getObjectMap(spyContext);
242+
map.put(Integer.valueOf(obj.objectId), new WeakReference<Object>(obj));
243+
return null;
244+
}).when(spyContext).retain(any(JSObject.class));
245+
doNothing().when(spyContext).cleanup();
246+
return spyContext;
247+
}
248+
249+
private Map getObjectMap(JavascriptContext context) throws Exception {
250+
java.lang.reflect.Field field = JavascriptContext.class.getDeclaredField("objectMap");
251+
field.setAccessible(true);
252+
return (Map) field.get(context);
253+
}
254+
}

0 commit comments

Comments
 (0)