package org.edx.mobile.view;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.view.View;
import android.widget.TextView;
import com.google.gson.JsonObject;
import org.edx.mobile.R;
import org.edx.mobile.http.OkHttpUtil;
import org.edx.mobile.model.Filter;
import org.edx.mobile.model.api.EnrolledCoursesResponse;
import org.edx.mobile.model.course.BlockType;
import org.edx.mobile.model.course.CourseComponent;
import org.edx.mobile.model.course.DiscussionBlockModel;
import org.edx.mobile.model.course.EncodedVideos;
import org.edx.mobile.model.course.HtmlBlockModel;
import org.edx.mobile.model.course.VideoBlockModel;
import org.edx.mobile.model.course.VideoData;
import org.edx.mobile.model.course.VideoInfo;
import org.edx.mobile.view.adapters.CourseUnitPagerAdapter;
import org.edx.mobile.view.custom.DisableableViewPager;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.mockito.Mockito;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
import org.robolectric.util.ActivityController;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import static org.assertj.android.api.Assertions.assertThat;
import static org.assertj.core.api.Java6Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.when;
public class CourseUnitNavigationActivityTest extends CourseBaseActivityTest {
/**
* Method for defining the subclass of {@link CourseUnitNavigationActivity}
* that is being tested. Should be overridden by subclasses.
*
* @return The {@link CourseUnitNavigationActivity} subclass that is being
* tested
*/
@Override
protected Class<? extends CourseUnitNavigationActivity> getActivityClass() {
return CourseUnitNavigationActivity.class;
}
/**
* {@inheritDoc}
*/
@Override
protected Intent getIntent() {
EnrolledCoursesResponse courseData;
CourseComponent courseComponent;
try {
courseData = api.getEnrolledCourses().get(0);
courseComponent = serviceManager.getCourseStructure(
courseData.getCourse().getId(),
OkHttpUtil.REQUEST_CACHE_TYPE.IGNORE_CACHE);
} catch (Exception e) {
throw new RuntimeException(e);
}
List<CourseComponent> leafComponents = new ArrayList<>();
courseComponent.fetchAllLeafComponents(leafComponents,
EnumSet.allOf(BlockType.class));
CourseComponent courseUnit = leafComponents.get(0);
Intent intent = super.getIntent();
Bundle extras = new Bundle();
extras.putSerializable(Router.EXTRA_COURSE_DATA, courseData);
extras.putString(Router.EXTRA_COURSE_COMPONENT_ID, courseUnit.getId());
intent.putExtra(Router.EXTRA_BUNDLE, extras);
return intent;
}
/**
* {@inheritDoc}
*/
@Override
protected boolean appliesPrevTransitionOnRestart() {
return true;
}
/**
* {@inheritDoc}
*/
@Test
@Override
public void initializeTest() {
super.initializeTest();
Intent intent = getIntent();
ActivityController<? extends CourseUnitNavigationActivity> controller =
Robolectric.buildActivity(getActivityClass()).withIntent(intent);
CourseUnitNavigationActivity activity = controller.get();
controller.create();
assertNotNull(activity.findViewById(R.id.course_unit_nav_bar));
View prev = activity.findViewById(R.id.goto_prev);
assertNotNull(prev);
assertThat(prev).isInstanceOf(TextView.class);
TextView prevButton = (TextView) prev;
View next = activity.findViewById(R.id.goto_next);
assertNotNull(next);
assertThat(next).isInstanceOf(TextView.class);
TextView nextButton = (TextView) next;
View prevUnitTitle = activity.findViewById(R.id.prev_unit_title);
assertNotNull(prevUnitTitle);
assertThat(prevUnitTitle).isInstanceOf(TextView.class);
TextView prevUnitLabel = (TextView) prevUnitTitle;
View nextUnitTitle = activity.findViewById(R.id.next_unit_title);
assertNotNull(nextUnitTitle);
assertThat(nextUnitTitle).isInstanceOf(TextView.class);
TextView nextUnitLabel = (TextView) nextUnitTitle;
View pager = activity.findViewById(R.id.pager);
assertNotNull(pager);
assertThat(pager).isInstanceOf(ViewPager.class);
ViewPager viewPager = (ViewPager) pager;
PagerAdapter pagerAdapter = viewPager.getAdapter();
assertNotNull(pagerAdapter);
// Text navigation through units
Bundle extras = intent.getBundleExtra(Router.EXTRA_BUNDLE);
EnrolledCoursesResponse courseData = (EnrolledCoursesResponse)
extras.getSerializable(Router.EXTRA_COURSE_DATA);
assertNotNull(courseData);
CourseComponent courseComponent;
try {
courseComponent = serviceManager.getCourseStructure(
courseData.getCourse().getId(),
OkHttpUtil.REQUEST_CACHE_TYPE.IGNORE_CACHE);
} catch (Exception e) {
throw new RuntimeException(e);
}
assertNotNull(courseComponent);
final String unitId = extras.getString(Router.EXTRA_COURSE_COMPONENT_ID);
assertNotNull(unitId);
CourseComponent currentUnit = courseComponent.find(new Filter<CourseComponent>() {
@Override
public boolean apply(CourseComponent courseComponent) {
return unitId.equals(courseComponent.getId());
}
});
assertNotNull(currentUnit);
// Since Robolectric current does not call the scroll callbacks due
// to not supporting view drawing (see
// https://github.com/robolectric/robolectric/issues/2007), we can't
// test the ViewPager navigation at the moment.
// TODO: Uncomment the following code when this issue is fixed
/*List<CourseComponent> units = new ArrayList<>();
courseComponent.fetchAllLeafComponents(units,
EnumSet.allOf(BlockType.class));
assertThat(units).isNotEmpty();
controller.start().postCreate((Bundle)null).resume().visible();
ListIterator<CourseComponent> unitIterator = units.listIterator(1);
for (CourseComponent prevUnit = null;;) {
int unitIndex = unitIterator.previousIndex();
CourseComponent nextUnit = unitIterator.hasNext() ?
unitIterator.next() : null;
verifyState(activity, unitIndex, currentUnit, prevUnit, nextUnit,
viewPager, pagerAdapter, prevButton, nextButton,
prevUnitLabel, nextUnitLabel);
if (nextUnit == null) break;
// The Scheduler needs to be paused while clicking the next button
// to enable the FragmentStatePagerAdapter to clear it's transaction
// state, and thus avoid the commit being called recursively
Scheduler foregroundScheduler = ShadowApplication.getInstance()
.getForegroundThreadScheduler();
foregroundScheduler.pause();
assertTrue(nextButton.performClick());
foregroundScheduler.unPause();
prevUnit = currentUnit;
currentUnit = nextUnit;
}
// Now iterate back in reverse order to test the previous button
unitIterator = units.listIterator(units.size() - 1);
for (CourseComponent nextUnit = null;;) {
int unitIndex = unitIterator.nextIndex();
CourseComponent prevUnit = unitIterator.hasPrevious() ?
unitIterator.previous() : null;
verifyState(activity, unitIndex, currentUnit, prevUnit, nextUnit,
viewPager, pagerAdapter, prevButton, nextButton,
prevUnitLabel, nextUnitLabel);
if (prevUnit == null) break;
Scheduler foregroundScheduler = ShadowApplication.getInstance()
.getForegroundThreadScheduler();
foregroundScheduler.pause();
assertTrue(prevButton.performClick());
foregroundScheduler.unPause();
nextUnit = currentUnit;
currentUnit = prevUnit;
}*/
}
/**
* Testing download progress menu visibility states and click behaviour
* (starting DownloadActivity). Only when both AppConstants.offline_flag
* is true and there is a downloading entry in the database, should the
* progress bar be visible.
*/
/**
* Generic method for verifying the state of the CourseUnitNavigationActivity
* at a specific unit.
*
* @param activity An instance of CourseUnitNavigationActivity that has been
* initialized
* @param unitIndex The index of the unit among all the leaves for the course
* @param currentUnit The selected course unit
* @param prevUnit The unit previous to the current selection
* @param nextUnit The unit next to the current selection
* @param viewPager The ViewPager instance containing the CourseUnitFragment
* instances
* @param pagerAdapter The PagerAdapter associated with the ViewPager
* @param prevButton The button for going to the previous unit
* @param nextButton The button for going to the next unit
* @param prevUnitLabel The label for the previous unit
* @param nextUnitLabel The label for the next unit
*/
private void verifyState(CourseUnitNavigationActivity activity,
int unitIndex, CourseComponent currentUnit,
CourseComponent prevUnit, CourseComponent nextUnit,
ViewPager viewPager, PagerAdapter pagerAdapter,
TextView prevButton, TextView nextButton,
TextView prevUnitLabel, TextView nextUnitLabel) {
assertTitle(activity, currentUnit.getDisplayName());
Class<? extends CourseUnitFragment> fragmentClass;
if (currentUnit instanceof VideoBlockModel) {
fragmentClass = CourseUnitVideoFragment.class;
} else if (!currentUnit.isMultiDevice() ){
fragmentClass = CourseUnitMobileNotSupportedFragment.class;
} else if (currentUnit.getType() != BlockType.VIDEO &&
currentUnit.getType() != BlockType.HTML &&
currentUnit.getType() != BlockType.OTHERS &&
currentUnit.getType() != BlockType.DISCUSSION &&
currentUnit.getType() != BlockType.PROBLEM ) {
fragmentClass = CourseUnitEmptyFragment.class;
} else if (currentUnit instanceof HtmlBlockModel) {
fragmentClass = CourseUnitWebViewFragment.class;
} else {
fragmentClass = CourseUnitMobileNotSupportedFragment.class;
}
Object item = pagerAdapter.instantiateItem(viewPager, unitIndex);
assertNotNull(item);
assertThat(item).isInstanceOf(fragmentClass);
Bundle args = ((Fragment) item).getArguments();
assertNotNull(args);
assertEquals(currentUnit, args.getSerializable(
Router.EXTRA_COURSE_UNIT));
assertEquals(prevUnit != null, prevButton.isEnabled());
assertEquals(nextUnit != null, nextButton.isEnabled());
CourseComponent prevSection = prevUnit == null ?
null : prevUnit.getParent();
CourseComponent nextSection = nextUnit == null ?
null : nextUnit.getParent();
if (prevSection == null ||
currentUnit.getParent().equals(prevSection)) {
assertThat(prevUnitLabel).isNotVisible();
assertThat(prevButton).hasText(R.string.assessment_previous);
} else {
assertThat(prevUnitLabel).isVisible();
assertThat(prevUnitLabel).hasText(prevSection.getDisplayName());
}
if (nextSection == null ||
currentUnit.getParent().equals(nextSection)) {
assertThat(nextUnitLabel).isNotVisible();
assertThat(nextButton).hasText(R.string.assessment_next);
} else {
assertThat(nextUnitLabel).isVisible();
assertThat(nextUnitLabel).hasText(nextSection.getDisplayName());
}
}
/**
* Generic method for asserting proper setup for the current orientation
*
* @param activity The current activity
*/
private void assertOrientationSetup(CourseUnitNavigationActivity activity) {
boolean isLandscape = activity.getResources().getConfiguration().orientation ==
Configuration.ORIENTATION_LANDSCAPE;
ActionBar bar = activity.getSupportActionBar();
View courseUnitNavBar = activity.findViewById(R.id.course_unit_nav_bar);
assertNotNull(courseUnitNavBar);
View pagerView = activity.findViewById(R.id.pager);
assertNotNull(pagerView);
assertThat(pagerView).isInstanceOf(DisableableViewPager.class);
assertEquals(true, (pagerView).isEnabled());
}
/**
* Generic method for testing the setup for the specified orientation
*
* @param orientation The orientation to be tested
*/
public void testOrientationSetup(int orientation) {
ActivityController<? extends CourseUnitNavigationActivity> controller =
Robolectric.buildActivity(getActivityClass()).withIntent(getIntent());
CourseUnitNavigationActivity activity = controller.get();
activity.getResources().getConfiguration().orientation = orientation;
controller.create();
assertOrientationSetup(activity);
}
/**
* Testing setup for different orientations
*/
@Test
public void orientationsSetupTest() {
testOrientationSetup(Configuration.ORIENTATION_PORTRAIT);
testOrientationSetup(Configuration.ORIENTATION_LANDSCAPE);
}
/**
* Generic method for testing setup on orientation changes
*
* @param activity The current activity
* @param orientation The orientation change to test
*/
private void testOrientationChange(
CourseUnitNavigationActivity activity, int orientation) {
Configuration config = activity.getResources().getConfiguration();
assertNotEquals(orientation, config.orientation);
config.orientation = orientation;
activity.onConfigurationChanged(config);
assertOrientationSetup(activity);
}
/**
* Testing orientation changes
*/
@Test
public void orientationChangeTest() {
CourseUnitNavigationActivity activity =
Robolectric.buildActivity(getActivityClass())
.withIntent(getIntent()).setup().get();
// Orientation needs to be explicitly set as PORTRAIT since its UNDEFINED initially
testOrientationChange(activity, Configuration.ORIENTATION_PORTRAIT);
assertEquals(Configuration.ORIENTATION_PORTRAIT,
activity.getResources().getConfiguration().orientation);
assertOrientationSetup(activity);
testOrientationChange(activity, Configuration.ORIENTATION_LANDSCAPE);
assertEquals(Configuration.ORIENTATION_LANDSCAPE,
activity.getResources().getConfiguration().orientation);
assertOrientationSetup(activity);
}
/**
* Testing creation of various fragments in the {@link CourseUnitNavigationActivity}'s
* ViewPager, by supplying its {@link CourseUnitPagerAdapter} with all possible
* {@link CourseComponent} models.
*/
@Test
public void testUnitFragmentCreation() {
FragmentManager fragmentManager = Mockito.mock(FragmentManager.class);
EnrolledCoursesResponse courseData = Mockito.mock(EnrolledCoursesResponse.class);
CourseUnitFragment.HasComponent hasComponent = Mockito.mock(CourseUnitFragment.HasComponent.class);
List<CourseComponent> unitList = new ArrayList<>();
List<Class<? extends CourseUnitFragment>> classesList = new ArrayList<>();
VideoBlockModel encodeVideosModel = Mockito.mock(VideoBlockModel.class);
VideoData videoData = Mockito.mock(VideoData.class);
videoData.encodedVideos = Mockito.mock(EncodedVideos.class);
when(videoData.encodedVideos.getPreferredVideoInfo())
.thenReturn(Mockito.mock(VideoInfo.class));
when(encodeVideosModel.getData()).thenReturn(videoData);
unitList.add(encodeVideosModel);
classesList.add(CourseUnitVideoFragment.class);
VideoBlockModel youtubeVideosModel = Mockito.mock(VideoBlockModel.class);
VideoData videoData2 = Mockito.mock(VideoData.class);
videoData2.encodedVideos = Mockito.mock(EncodedVideos.class);
when(videoData2.encodedVideos.getYoutubeVideoInfo())
.thenReturn(Mockito.mock(VideoInfo.class));
when(youtubeVideosModel.getData()).thenReturn(videoData2);
unitList.add(youtubeVideosModel);
classesList.add(CourseUnitOnlyOnYoutubeFragment.class);
DiscussionBlockModel discussionModel = Mockito.mock(DiscussionBlockModel.class);
unitList.add(discussionModel);
if (config.isDiscussionsEnabled()) {
classesList.add(CourseUnitDiscussionFragment.class);
} else {
classesList.add(CourseUnitMobileNotSupportedFragment.class);
}
CourseComponent nonMultiDeviceModel = Mockito.mock(CourseComponent.class);
when(nonMultiDeviceModel.isMultiDevice()).thenReturn(false);
unitList.add(nonMultiDeviceModel);
classesList.add(CourseUnitMobileNotSupportedFragment.class);
HtmlBlockModel htmlModel = Mockito.mock(HtmlBlockModel.class);
when(htmlModel.isMultiDevice()).thenReturn(true);
when(htmlModel.getType()).thenReturn(BlockType.HTML);
unitList.add(htmlModel);
classesList.add(CourseUnitWebViewFragment.class);
CourseComponent unknownModel = Mockito.mock(CourseComponent.class);
when(unknownModel.isMultiDevice()).thenReturn(true);
when(unknownModel.getType()).thenReturn(BlockType.COURSE);
unitList.add(unknownModel);
classesList.add(CourseUnitEmptyFragment.class);
CourseComponent problemModel = Mockito.mock(CourseComponent.class);
when(problemModel.isMultiDevice()).thenReturn(true);
when(problemModel.getType()).thenReturn(BlockType.PROBLEM);
unitList.add(problemModel);
classesList.add(CourseUnitMobileNotSupportedFragment.class);
CourseComponent othersModel = Mockito.mock(CourseComponent.class);
when(othersModel.isMultiDevice()).thenReturn(true);
when(othersModel.getType()).thenReturn(BlockType.OTHERS);
unitList.add(othersModel);
classesList.add(CourseUnitMobileNotSupportedFragment.class);
CourseUnitPagerAdapter adapter = new CourseUnitPagerAdapter(fragmentManager, config,
unitList, courseData, hasComponent);
for (int size = unitList.size(), i = 0; i < size; i++) {
assertThat(adapter.getItem(i)).isInstanceOf(classesList.get(i));
}
}
/**
* Tests creation of various fragments in the {@link CourseUnitNavigationActivity}'s
* ViewPager, by supplying its {@link CourseUnitPagerAdapter} with a single
* {@link CourseComponent} model at a time via parameterization.
*/
// TODO: Robolectric doesn't have a Gradle-based parameterized test suite, need to implement it.
@Ignore
@RunWith(Parameterized.class)
public static class ParameterizedFragmentCreation extends UiTest {
private static final String DISCUSSIONS_ENABLED = "DISCUSSIONS_ENABLED";
@Parameterized.Parameters
public static Collection<Object[]> data() {
List<Object[]> argsList = new ArrayList<>();
{
VideoBlockModel encodedVideosModel = Mockito.mock(VideoBlockModel.class);
VideoData videoData = Mockito.mock(VideoData.class);
videoData.encodedVideos = Mockito.mock(EncodedVideos.class);
when(videoData.encodedVideos.getPreferredVideoInfo())
.thenReturn(Mockito.mock(VideoInfo.class));
when(encodedVideosModel.getData()).thenReturn(videoData);
argsList.add(new Object[] {encodedVideosModel, CourseUnitVideoFragment.class, true});
VideoBlockModel youtubeVideosModel = Mockito.mock(VideoBlockModel.class);
VideoData videoData2 = Mockito.mock(VideoData.class);
videoData2.encodedVideos = Mockito.mock(EncodedVideos.class);
when(videoData2.encodedVideos.getYoutubeVideoInfo())
.thenReturn(Mockito.mock(VideoInfo.class));
when(youtubeVideosModel.getData()).thenReturn(videoData2);
argsList.add(new Object[] {youtubeVideosModel, CourseUnitOnlyOnYoutubeFragment.class, true});
DiscussionBlockModel discussionModel = Mockito.mock(DiscussionBlockModel.class);
argsList.add(new Object[] {discussionModel, CourseUnitDiscussionFragment.class, true});
argsList.add(new Object[] {discussionModel, CourseUnitMobileNotSupportedFragment.class, false});
CourseComponent nonMultiDeviceModel = Mockito.mock(CourseComponent.class);
when(nonMultiDeviceModel.isMultiDevice()).thenReturn(false);
argsList.add(new Object[] {nonMultiDeviceModel, CourseUnitMobileNotSupportedFragment.class, true});
HtmlBlockModel htmlModel = Mockito.mock(HtmlBlockModel.class);
when(htmlModel.isMultiDevice()).thenReturn(true);
when(htmlModel.getType()).thenReturn(BlockType.HTML);
argsList.add(new Object[] {htmlModel, CourseUnitWebViewFragment.class, true});
CourseComponent unknownModel = Mockito.mock(CourseComponent.class);
when(unknownModel.isMultiDevice()).thenReturn(true);
when(unknownModel.getType()).thenReturn(BlockType.COURSE);
argsList.add(new Object[] {unknownModel, CourseUnitEmptyFragment.class, true});
CourseComponent problemModel = Mockito.mock(CourseComponent.class);
when(problemModel.isMultiDevice()).thenReturn(true);
when(problemModel.getType()).thenReturn(BlockType.PROBLEM);
argsList.add(new Object[] {problemModel, CourseUnitMobileNotSupportedFragment.class, true});
CourseComponent othersModel = Mockito.mock(CourseComponent.class);
when(othersModel.isMultiDevice()).thenReturn(true);
when(othersModel.getType()).thenReturn(BlockType.OTHERS);
argsList.add(new Object[] {othersModel, CourseUnitMobileNotSupportedFragment.class, true});
}
return argsList;
}
/**
* The {@link CourseComponent} that we provide to the {@link CourseUnitPagerAdapter} as input.
*/
@Parameterized.Parameter
public CourseComponent paramCourseComponent;
/**
* The {@link Fragment} that we expect the {@link CourseUnitPagerAdapter} will create an
* instance of.
*/
@Parameterized.Parameter(value = 1)
public Class expectedFragmentClass;
/**
* A flag denoting whether inline discussions should be enabled in the config, that is
* provided as a parameter.
*/
@Parameterized.Parameter(value = 2)
public boolean isDiscussionsEnabled;
@Test
public void test() throws IOException {
config = new org.edx.mobile.util.Config(generateConfigProperties());
FragmentManager fragmentManager = Mockito.mock(FragmentManager.class);
EnrolledCoursesResponse courseData = Mockito.mock(EnrolledCoursesResponse.class);
CourseUnitFragment.HasComponent hasComponent = Mockito.mock(CourseUnitFragment.HasComponent.class);
CourseUnitPagerAdapter adapter = new CourseUnitPagerAdapter(fragmentManager, config,
Collections.singletonList(paramCourseComponent), courseData, hasComponent);
assertThat(adapter.getItem(0)).isInstanceOf(expectedFragmentClass);
}
@Override
protected JsonObject generateConfigProperties() throws IOException {
JsonObject properties = super.generateConfigProperties();
properties.addProperty(DISCUSSIONS_ENABLED, isDiscussionsEnabled);
return properties;
}
}
}