1package com.xtremelabs.robolectric.res;
2
3import android.app.Activity;
4import android.content.Context;
5import android.os.Build;
6import android.os.Bundle;
7import android.view.View;
8import android.view.ViewGroup;
9import android.webkit.WebView;
10import android.widget.*;
11import com.google.android.maps.MapView;
12import com.xtremelabs.robolectric.R;
13import com.xtremelabs.robolectric.Robolectric;
14import com.xtremelabs.robolectric.WithTestDefaultsRunner;
15import com.xtremelabs.robolectric.shadows.ShadowImageView;
16import com.xtremelabs.robolectric.shadows.ShadowTextView;
17import com.xtremelabs.robolectric.util.CustomView;
18import com.xtremelabs.robolectric.util.CustomView2;
19import com.xtremelabs.robolectric.util.I18nException;
20import com.xtremelabs.robolectric.util.TestUtil;
21import org.junit.Before;
22import org.junit.Test;
23import org.junit.runner.RunWith;
24
25import static com.xtremelabs.robolectric.Robolectric.shadowOf;
26import static com.xtremelabs.robolectric.util.TestUtil.*;
27import static org.hamcrest.CoreMatchers.*;
28import static org.junit.Assert.*;
29
30@RunWith(WithTestDefaultsRunner.class)
31public class ViewLoaderTest {
32    private ViewLoader viewLoader;
33    private Context context;
34
35    @Before
36    public void setUp() throws Exception {
37        Robolectric.bindDefaultShadowClasses();
38
39        ResourceExtractor resourceExtractor = new ResourceExtractor();
40        resourceExtractor.addLocalRClass(R.class);
41        resourceExtractor.addSystemRClass(android.R.class);
42
43        StringResourceLoader stringResourceLoader = new StringResourceLoader(resourceExtractor);
44        new DocumentLoader(stringResourceLoader).loadResourceXmlDir(resourceFile("res", "values"));
45        new DocumentLoader(stringResourceLoader).loadSystemResourceXmlDir(getSystemResourceDir("values"));
46
47        viewLoader =  new ViewLoader(resourceExtractor, new AttrResourceLoader(resourceExtractor));
48        new DocumentLoader(viewLoader).loadResourceXmlDir(resourceFile("res", "layout"));
49        new DocumentLoader(viewLoader).loadResourceXmlDir(resourceFile("res", "layout-land"));
50        new DocumentLoader(viewLoader).loadResourceXmlDir(resourceFile("res", "layout-large-v16"));
51        new DocumentLoader(viewLoader).loadResourceXmlDir(resourceFile("res", "layout-v11"));
52        new DocumentLoader(viewLoader).loadResourceXmlDir(resourceFile("res", "layout-xlarge"));
53        new DocumentLoader(viewLoader).loadResourceXmlDir(resourceFile("res", "layout-xlarge-v11"));
54        new DocumentLoader(viewLoader).loadSystemResourceXmlDir(getSystemResourceDir("layout"));
55
56        context = new Activity();
57    }
58
59    @Test
60    public void testCreatesCorrectClasses() throws Exception {
61        ViewGroup view = (ViewGroup) viewLoader.inflateView(context, "layout/media");
62        TestUtil.assertInstanceOf(LinearLayout.class, view);
63
64        assertSame(context, view.getContext());
65    }
66
67    @Test
68    public void testChoosesLayoutBasedOnDefaultScreenSize() throws Exception {
69        ViewGroup view = (ViewGroup) viewLoader.inflateView(context, "layout/different_screen_sizes");
70        TextView textView = (TextView) view.findViewById(android.R.id.text1);
71        assertThat(textView.getText().toString(), equalTo("default"));
72    }
73
74    @Test
75    public void testChoosesLayoutBasedOnSearchPath_choosesFirstFileFoundOnPath() throws Exception {
76        viewLoader.setLayoutQualifierSearchPath("xlarge", "land");
77        ViewGroup view = (ViewGroup) viewLoader.inflateView(context, "layout/different_screen_sizes");
78        TextView textView = (TextView) view.findViewById(android.R.id.text1);
79        assertThat(textView.getText().toString(), equalTo("xlarge"));
80    }
81
82    @Test
83    public void testChoosesLayoutBasedOnSearchPath_respectsOrderOfPath() throws Exception {
84        viewLoader.setLayoutQualifierSearchPath("does-not-exist", "land", "xlarge");
85        ViewGroup view = (ViewGroup) viewLoader.inflateView(context, "layout/different_screen_sizes");
86        TextView textView = (TextView) view.findViewById(android.R.id.text1);
87        assertThat(textView.getText().toString(), equalTo("land"));
88    }
89
90    @Test
91    public void testChoosesLayoutBasedOnDefaultVersion() throws Exception {
92        Robolectric.Reflection.setFinalStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.FROYO);
93        ViewGroup view = (ViewGroup) viewLoader.inflateView(context, "layout/different_screen_sizes");
94        TextView textView = (TextView) view.findViewById(android.R.id.text1);
95        assertThat(textView.getText().toString(), equalTo("default"));
96    }
97
98    @Test
99    public void testChoosesLayoutBasedOnNewestVersion() throws Exception {
100        Robolectric.Reflection.setFinalStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.JELLY_BEAN);
101        ViewGroup view = (ViewGroup) viewLoader.inflateView(context, "layout/different_screen_sizes");
102        TextView textView = (TextView) view.findViewById(android.R.id.text1);
103        assertThat(textView.getText().toString(), equalTo("v11"));
104    }
105
106    @Test
107    public void testChoosesLayoutBasedOnSearchPath_choosesFirstFileFoundOnPathWithVersionNumber() throws Exception {
108        viewLoader.setLayoutQualifierSearchPath("xlarge", "large");
109        Robolectric.Reflection.setFinalStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.HONEYCOMB);
110        ViewGroup view = (ViewGroup) viewLoader.inflateView(context, "layout/different_screen_sizes");
111        TextView textView = (TextView) view.findViewById(android.R.id.text1);
112        assertThat(textView.getText().toString(), equalTo("xlarge-v11"));
113    }
114
115    @Test
116    public void testChoosesLayoutBasedOnSearchPath_choosesBestFileFoundOnPathWithVersionNumber() throws Exception {
117        viewLoader.setLayoutQualifierSearchPath("xlarge", "large");
118        Robolectric.Reflection.setFinalStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.JELLY_BEAN);
119        ViewGroup view = (ViewGroup) viewLoader.inflateView(context, "layout/different_screen_sizes");
120        TextView textView = (TextView) view.findViewById(android.R.id.text1);
121        assertThat(textView.getText().toString(), equalTo("xlarge-v11"));
122    }
123
124    @Test
125    public void testWebView() throws Exception {
126        ViewGroup view = (ViewGroup) viewLoader.inflateView(context, "layout/webview_holder");
127        WebView webView = (WebView) view.findViewById(R.id.web_view);
128
129        webView.loadUrl("www.example.com");
130
131        assertThat(shadowOf(webView).getLastLoadedUrl(), equalTo("www.example.com"));
132    }
133
134    @Test
135    public void testAddsChildren() throws Exception {
136        ViewGroup view = (ViewGroup) viewLoader.inflateView(context, "layout/media");
137        assertTrue(view.getChildCount() > 0);
138
139        assertSame(context, view.getChildAt(0).getContext());
140    }
141
142    @Test
143    public void testFindsChildrenById() throws Exception {
144        ViewGroup mediaView = (ViewGroup) viewLoader.inflateView(context, "layout/media");
145        TestUtil.assertInstanceOf(TextView.class, mediaView.findViewById(R.id.title));
146
147        ViewGroup mainView = (ViewGroup) viewLoader.inflateView(context, "layout/main");
148        assertInstanceOf(View.class, mainView.findViewById(R.id.title));
149    }
150
151    @Test
152    public void testInflatingConflictingSystemAndLocalViewsWorks() throws Exception {
153        ViewGroup view = (ViewGroup) viewLoader.inflateView(context, "layout/activity_list_item");
154        assertInstanceOf(ImageView.class, view.findViewById(R.id.icon));
155
156        view = (ViewGroup) viewLoader.inflateView(context, "android:layout/activity_list_item");
157        assertInstanceOf(ImageView.class, view.findViewById(android.R.id.icon));
158    }
159
160    @Test
161    public void testInclude() throws Exception {
162        ViewGroup mediaView = (ViewGroup) viewLoader.inflateView(context, "layout/media");
163        assertInstanceOf(TextView.class, mediaView.findViewById(R.id.include_id));
164    }
165
166    @Test
167    public void testIncludeShouldRetainAttributes() throws Exception {
168        ViewGroup mediaView = (ViewGroup) viewLoader.inflateView(context, "layout/media");
169        assertThat(mediaView.findViewById(R.id.include_id).getVisibility(), is(View.GONE));
170    }
171
172    @Test
173    public void shouldOverwriteIdOnIncludedNonMerge() throws Exception {
174        ViewGroup mediaView = (ViewGroup) viewLoader.inflateView(context, "layout/media");
175        assertNull(mediaView.findViewById(R.id.snippet_text));
176    }
177
178    @Test
179    public void shouldRetainIdOnIncludedMergeWhenIncludeSpecifiesNoId() throws Exception {
180        ViewGroup mediaView = (ViewGroup) viewLoader.inflateView(context, "layout/override_include");
181        assertInstanceOf(TextView.class, mediaView.findViewById(R.id.inner_text));
182    }
183
184    @Test
185    public void shouldRetainIdOnIncludedNonMergeWhenIncludeSpecifiesNoId() throws Exception {
186        ViewGroup mediaView = (ViewGroup) viewLoader.inflateView(context, "layout/override_include");
187        assertInstanceOf(TextView.class, mediaView.findViewById(R.id.snippet_text));
188    }
189
190    @Test
191    public void testIncludedIdShouldNotBeFoundWhenIncludedIsMerge() throws Exception {
192        ViewGroup overrideIncludeView = (ViewGroup) viewLoader.inflateView(context, "layout/outer");
193        assertInstanceOf(LinearLayout.class, overrideIncludeView.findViewById(R.id.outer_merge));
194        assertInstanceOf(TextView.class, overrideIncludeView.findViewById(R.id.inner_text));
195        assertNull(overrideIncludeView.findViewById(R.id.include_id));
196        assertEquals(1, overrideIncludeView.getChildCount());
197    }
198
199    @Test
200    public void testIncludeShouldOverrideAttributesOfIncludedRootNode() throws Exception {
201        ViewGroup overrideIncludeView = (ViewGroup) viewLoader.inflateView(context, "layout/override_include");
202        assertThat(overrideIncludeView.findViewById(R.id.snippet_text).getVisibility(), is(View.INVISIBLE));
203    }
204
205    @Test
206    public void shouldNotCountRequestFocusElementAsChild() throws Exception {
207        ViewGroup viewGroup = (ViewGroup) viewLoader.inflateView(context, "layout/request_focus");
208        ViewGroup frameLayout = (ViewGroup) viewGroup.getChildAt(1);
209        assertEquals(0, frameLayout.getChildCount());
210    }
211
212    @Test
213    public void shouldGiveFocusToElementContainingRequestFocusElement() throws Exception {
214        ViewGroup viewGroup = (ViewGroup) viewLoader.inflateView(context, "layout/request_focus");
215        EditText editText = (EditText) viewGroup.findViewById(R.id.edit_text);
216        assertFalse(editText.isFocused());
217    }
218
219    @Test
220    public void shouldGiveFocusToFirstFocusableElement_butThisMightBeTheWrongBehavior() throws Exception {
221        ViewGroup viewGroup = (ViewGroup) viewLoader.inflateView(context, "layout/request_focus_with_two_edit_texts");
222        assertTrue(viewGroup.findViewById(R.id.edit_text).isFocused());
223        assertFalse(viewGroup.findViewById(R.id.edit_text2).isFocused());
224    }
225
226    @Test
227    public void testMerge() throws Exception {
228        ViewGroup mediaView = (ViewGroup) viewLoader.inflateView(context, "layout/outer");
229        TestUtil.assertInstanceOf(TextView.class, mediaView.findViewById(R.id.inner_text));
230    }
231
232    @Test
233    public void mergeIncludesShouldNotCreateAncestryLoops() throws Exception {
234        ViewGroup mediaView = (ViewGroup) viewLoader.inflateView(context, "layout/outer");
235        mediaView.hasFocus();
236    }
237
238    @Test
239    public void testViewGroupsLooksAtItsOwnId() throws Exception {
240        TextView mediaView = (TextView) viewLoader.inflateView(context, "layout/snippet");
241        assertSame(mediaView, mediaView.findViewById(R.id.snippet_text));
242    }
243
244    @Test
245    public void shouldConstructCustomViewsWithAttributesConstructor() throws Exception {
246        CustomView view = (CustomView) viewLoader.inflateView(context, "layout/custom_layout");
247        assertThat(view.attributeResourceValue, equalTo(R.string.hello));
248    }
249
250    @Test
251    public void testViewVisibilityIsSet() throws Exception {
252        View mediaView = viewLoader.inflateView(context, "layout/media");
253        assertThat(mediaView.findViewById(R.id.title).getVisibility(), equalTo(View.VISIBLE));
254        assertThat(mediaView.findViewById(R.id.subtitle).getVisibility(), equalTo(View.GONE));
255    }
256
257    @Test
258    public void testTextViewTextIsSet() throws Exception {
259        View mediaView = viewLoader.inflateView(context, "layout/main");
260        assertThat(((TextView) mediaView.findViewById(R.id.title)).getText().toString(), equalTo("Main Layout"));
261        assertThat(((TextView) mediaView.findViewById(R.id.subtitle)).getText().toString(), equalTo("Hello"));
262    }
263
264    @Test
265    public void testTextViewCompoundDrawablesAreSet() throws Exception {
266        View mediaView = viewLoader.inflateView(context, "layout/main");
267        ShadowTextView shadowTextView = shadowOf((TextView) mediaView.findViewById(R.id.title));
268
269        assertThat(shadowTextView.getCompoundDrawablesImpl().getTop(), equalTo(R.drawable.an_image));
270        assertThat(shadowTextView.getCompoundDrawablesImpl().getRight(), equalTo(R.drawable.an_other_image));
271        assertThat(shadowTextView.getCompoundDrawablesImpl().getBottom(), equalTo(R.drawable.third_image));
272        assertThat(shadowTextView.getCompoundDrawablesImpl().getLeft(), equalTo(R.drawable.fourth_image));
273    }
274
275    @Test
276    public void testCheckBoxCheckedIsSet() throws Exception {
277        View mediaView = viewLoader.inflateView(context, "layout/main");
278        assertThat(((CheckBox) mediaView.findViewById(R.id.true_checkbox)).isChecked(), equalTo(true));
279        assertThat(((CheckBox) mediaView.findViewById(R.id.false_checkbox)).isChecked(), equalTo(false));
280        assertThat(((CheckBox) mediaView.findViewById(R.id.default_checkbox)).isChecked(), equalTo(false));
281    }
282
283    @Test
284    public void testImageViewSrcIsSet() throws Exception {
285        View mediaView = viewLoader.inflateView(context, "layout/main");
286        assertThat(((ShadowImageView) shadowOf(mediaView.findViewById(R.id.image))).getResourceId(), equalTo(R.drawable.an_image));
287    }
288
289    @Test
290    public void shouldInflateMergeLayoutIntoParent() throws Exception {
291        View innerMerge = viewLoader.inflateView(context, R.layout.inner_merge, new LinearLayout(null));
292        assertNotNull(innerMerge);
293    }
294
295    @Test
296    public void testMapView() throws Exception {
297        RelativeLayout mainView = (RelativeLayout) viewLoader.inflateView(context, "layout/mapview");
298        TestUtil.assertInstanceOf(MapView.class, mainView.findViewById(R.id.map_view));
299    }
300
301    @Test
302    public void testViewEnabled() throws Exception {
303        View mediaView = viewLoader.inflateView(context, "layout/main");
304        assertThat(mediaView.findViewById(R.id.time).isEnabled(), equalTo(false));
305    }
306
307    @Test
308    public void testContentDescriptionIsSet() throws Exception {
309        View mediaView = viewLoader.inflateView(context, "layout/main");
310        assertThat(mediaView.findViewById(R.id.time).getContentDescription().toString(), equalTo("Howdy"));
311    }
312
313    @Test
314    public void testViewBackgroundIdIsSet() throws Exception {
315        View mediaView = viewLoader.inflateView(context, "layout/main");
316        ImageView imageView = (ImageView) mediaView.findViewById(R.id.image);
317        ShadowImageView shadowImageView = Robolectric.shadowOf(imageView);
318
319        assertThat(shadowImageView.getBackgroundResourceId(), equalTo(R.drawable.image_background));
320    }
321
322    @Test
323    public void testOnClickAttribute() throws Exception {
324        ClickActivity activity = new ClickActivity();
325        activity.onCreate(null);
326
327        assertThat(activity.clicked, equalTo(false));
328
329        Button button = (Button)activity.findViewById(R.id.button);
330        button.performClick();
331
332        assertThat(activity.clicked, equalTo(true));
333    }
334
335    @Test
336    public void testInvalidOnClickAttribute() throws Exception {
337        Activity activity = new Activity();
338        activity.setContentView(R.layout.with_invalid_onclick);
339
340        Button button =
341            (Button)activity.findViewById(R.id.invalid_onclick_button);
342
343        IllegalStateException exception = null;
344        try {
345            button.performClick();
346        } catch (IllegalStateException e) {
347            exception = e;
348        } finally {
349            assertNotNull(exception);
350            assertThat("The error message should contain the id name of the "
351                       + "faulty button",
352                       exception.getMessage(),
353                       containsString("invalid_onclick_button"));
354        }
355    }
356
357    @Test
358    public void shouldInvokeOnFinishInflate() throws Exception {
359        CustomView2 outerCustomView = (CustomView2) viewLoader.inflateView(context, "layout/custom_layout2");
360        CustomView2 innerCustomView = (CustomView2) outerCustomView.getChildAt(0);
361        assertThat(outerCustomView.childCountAfterInflate, equalTo(1));
362        assertThat(innerCustomView.childCountAfterInflate, equalTo(3));
363    }
364
365    @Test
366    public void testIncludesLinearLayoutsOnlyOnce() throws Exception {
367        ViewGroup parentView = (ViewGroup) viewLoader.inflateView(context, "layout/included_layout_parent");
368        assertEquals(1, parentView.getChildCount());
369    }
370
371    @Test(expected=I18nException.class)
372    public void shouldThrowI18nExceptionOnLayoutWithBareStrings() throws Exception {
373    	viewLoader.setStrictI18n(true);
374        new DocumentLoader(viewLoader).loadResourceXmlDir(resourceFile("res", "layout"));
375        new DocumentLoader(viewLoader).loadSystemResourceXmlDir(getSystemResourceDir("layout"));
376
377    	viewLoader.inflateView(context,"layout/text_views");
378    }
379
380    public static class ClickActivity extends Activity {
381        public boolean clicked = false;
382
383        @Override protected void onCreate(Bundle savedInstanceState) {
384            super.onCreate(savedInstanceState);
385            setContentView(R.layout.main);
386        }
387
388        public void onButtonClick(View v) {
389            clicked = true;
390        }
391    }
392}
393