-
Notifications
You must be signed in to change notification settings - Fork 527
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
…176) * Introduce initially viable TopicListController interface with tests, and no real implementation. This commit includes thumbnail structures that should be split into its own branch/PR since other controllers will depend on it. * Introduce 6 sample thumbnail graphics locally and a proto data structure for thumbnails that can be used for lessons. * Update topic list controller to use new-and-improved thumbnail options. * Fix post-merge breakages. * Address review comment.
- Loading branch information
1 parent
4968ea4
commit 4867dd9
Showing
3 changed files
with
451 additions
and
0 deletions.
There are no files selected for viewing
117 changes: 117 additions & 0 deletions
117
domain/src/main/java/org/oppia/domain/topic/TopicListController.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
package org.oppia.domain.topic | ||
|
||
import androidx.lifecycle.LiveData | ||
import androidx.lifecycle.MutableLiveData | ||
import java.util.concurrent.TimeUnit | ||
import javax.inject.Inject | ||
import javax.inject.Singleton | ||
import org.oppia.app.model.LessonThumbnail | ||
import org.oppia.app.model.LessonThumbnailGraphic | ||
import org.oppia.app.model.OngoingStoryList | ||
import org.oppia.app.model.PromotedStory | ||
import org.oppia.app.model.TopicList | ||
import org.oppia.app.model.TopicSummary | ||
import org.oppia.util.data.AsyncResult | ||
|
||
const val TEST_TOPIC_ID_0 = "test_topic_id_0" | ||
const val TEST_TOPIC_ID_1 = "test_topic_id_1" | ||
|
||
private val EVICTION_TIME_MILLIS = TimeUnit.DAYS.toMillis(1) | ||
|
||
/** Controller for retrieving the list of topics available to the learner to play. */ | ||
@Singleton | ||
class TopicListController @Inject constructor() { | ||
/** | ||
* Returns the list of [TopicSummary]s currently tracked by the app, possibly up to | ||
* [EVICTION_TIME_MILLIS] old. | ||
*/ | ||
fun getTopicList(): LiveData<AsyncResult<TopicList>> { | ||
return MutableLiveData(AsyncResult.success(createTopicList())) | ||
} | ||
|
||
/** | ||
* Returns the list of ongoing [PromotedStory]s that can be viewed via a link on the homescreen. The total number of | ||
* promoted stories should correspond to the ongoing story count within the [TopicList] returned by [getTopicList]. | ||
*/ | ||
fun getOngoingStoryList(): LiveData<AsyncResult<OngoingStoryList>> { | ||
return MutableLiveData(AsyncResult.success(createOngoingStoryList())) | ||
} | ||
|
||
private fun createTopicList(): TopicList { | ||
return TopicList.newBuilder() | ||
.setPromotedStory(createPromotedStory1()) | ||
.addTopicSummary(createTopicSummary0()) | ||
.addTopicSummary(createTopicSummary1()) | ||
.setOngoingStoryCount(2) | ||
.build() | ||
} | ||
|
||
private fun createTopicSummary0(): TopicSummary { | ||
return TopicSummary.newBuilder() | ||
.setTopicId(TEST_TOPIC_ID_0) | ||
.setName("First Topic") | ||
.setVersion(1) | ||
.setSubtopicCount(0) | ||
.setCanonicalStoryCount(2) | ||
.setUncategorizedSkillCount(0) | ||
.setAdditionalStoryCount(0) | ||
.setTotalSkillCount(2) | ||
.setTotalChapterCount(4) | ||
.setTopicThumbnail(createTopicThumbnail0()) | ||
.build() | ||
} | ||
|
||
private fun createTopicSummary1(): TopicSummary { | ||
return TopicSummary.newBuilder() | ||
.setTopicId(TEST_TOPIC_ID_1) | ||
.setName("Second Topic") | ||
.setVersion(3) | ||
.setSubtopicCount(0) | ||
.setCanonicalStoryCount(1) | ||
.setUncategorizedSkillCount(0) | ||
.setAdditionalStoryCount(0) | ||
.setTotalSkillCount(1) | ||
.setTotalChapterCount(1) | ||
.setTopicThumbnail(createTopicThumbnail1()) | ||
.build() | ||
} | ||
|
||
private fun createOngoingStoryList(): OngoingStoryList { | ||
return OngoingStoryList.newBuilder() | ||
.addRecentStory(createPromotedStory1()) | ||
.build() | ||
} | ||
|
||
private fun createPromotedStory1(): PromotedStory { | ||
return PromotedStory.newBuilder() | ||
.setStoryId(TEST_STORY_ID_1) | ||
.setStoryName("Second Story") | ||
.setTopicId(TEST_TOPIC_ID_0) | ||
.setTopicName("First Topic") | ||
.setCompletedChapterCount(1) | ||
.setTotalChapterCount(3) | ||
.setLessonThumbnail(createStoryThumbnail()) | ||
.build() | ||
} | ||
|
||
private fun createTopicThumbnail0(): LessonThumbnail { | ||
return LessonThumbnail.newBuilder() | ||
.setThumbnailGraphic(LessonThumbnailGraphic.CHILD_WITH_BOOK) | ||
.setBackgroundColorRgb(0xd5836f) | ||
.build() | ||
} | ||
|
||
private fun createTopicThumbnail1(): LessonThumbnail { | ||
return LessonThumbnail.newBuilder() | ||
.setThumbnailGraphic(LessonThumbnailGraphic.CHILD_WITH_CUPCAKES) | ||
.setBackgroundColorRgb(0xf7bf73) | ||
.build() | ||
} | ||
|
||
private fun createStoryThumbnail(): LessonThumbnail { | ||
return LessonThumbnail.newBuilder() | ||
.setThumbnailGraphic(LessonThumbnailGraphic.DUCK_AND_CHICKEN) | ||
.setBackgroundColorRgb(0xa5d3ec) | ||
.build() | ||
} | ||
} |
242 changes: 242 additions & 0 deletions
242
domain/src/test/java/org/oppia/domain/topic/TopicListControllerTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,242 @@ | ||
package org.oppia.domain.topic | ||
|
||
import android.app.Application | ||
import android.content.Context | ||
import androidx.test.core.app.ApplicationProvider | ||
import androidx.test.ext.junit.runners.AndroidJUnit4 | ||
import com.google.common.truth.Truth.assertThat | ||
import dagger.BindsInstance | ||
import dagger.Component | ||
import dagger.Module | ||
import dagger.Provides | ||
import org.junit.Before | ||
import org.junit.Test | ||
import org.junit.runner.RunWith | ||
import org.oppia.app.model.LessonThumbnailGraphic | ||
import org.robolectric.annotation.Config | ||
import javax.inject.Inject | ||
import javax.inject.Singleton | ||
|
||
/** Tests for [TopicListController]. */ | ||
@RunWith(AndroidJUnit4::class) | ||
@Config(manifest = Config.NONE) | ||
class TopicListControllerTest { | ||
@Inject | ||
lateinit var topicListController: TopicListController | ||
|
||
@Before | ||
fun setUp() { | ||
setUpTestApplicationComponent() | ||
} | ||
|
||
// TODO(#15): Add tests for recommended lessons rather than promoted, and tests for the 'continue playing' LiveData | ||
// not providing any data for cases when there are no ongoing lessons. Also, add tests for other uncovered cases | ||
// (such as having and not having lessons in either of the OngoingStoryList section, or AsyncResult errors). | ||
|
||
@Test | ||
fun testRetrieveTopicList_isSuccessful() { | ||
val topicListLiveData = topicListController.getTopicList() | ||
|
||
val topicListResult = topicListLiveData.value | ||
assertThat(topicListResult).isNotNull() | ||
assertThat(topicListResult!!.isSuccess()).isTrue() | ||
} | ||
|
||
@Test | ||
fun testRetrieveTopicList_providesListOfMultipleTopics() { | ||
val topicListLiveData = topicListController.getTopicList() | ||
|
||
val topicList = topicListLiveData.value!!.getOrThrow() | ||
assertThat(topicList.topicSummaryCount).isGreaterThan(1) | ||
} | ||
|
||
@Test | ||
fun testRetrieveTopicList_firstTopic_hasCorrectTopicInfo() { | ||
val topicListLiveData = topicListController.getTopicList() | ||
|
||
val topicList = topicListLiveData.value!!.getOrThrow() | ||
val firstTopic = topicList.getTopicSummary(0) | ||
assertThat(firstTopic.topicId).isEqualTo(TEST_TOPIC_ID_0) | ||
assertThat(firstTopic.name).isEqualTo("First Topic") | ||
} | ||
|
||
@Test | ||
fun testRetrieveTopicList_firstTopic_hasCorrectThumbnail() { | ||
val topicListLiveData = topicListController.getTopicList() | ||
|
||
val topicList = topicListLiveData.value!!.getOrThrow() | ||
val firstTopic = topicList.getTopicSummary(0) | ||
assertThat(firstTopic.topicThumbnail.thumbnailGraphic).isEqualTo(LessonThumbnailGraphic.CHILD_WITH_BOOK) | ||
} | ||
|
||
@Test | ||
fun testRetrieveTopicList_firstTopic_hasCorrectLessonCount() { | ||
val topicListLiveData = topicListController.getTopicList() | ||
|
||
val topicList = topicListLiveData.value!!.getOrThrow() | ||
val firstTopic = topicList.getTopicSummary(0) | ||
assertThat(firstTopic.totalChapterCount).isEqualTo(4) | ||
} | ||
|
||
@Test | ||
fun testRetrieveTopicList_secondTopic_hasCorrectTopicInfo() { | ||
val topicListLiveData = topicListController.getTopicList() | ||
|
||
val topicList = topicListLiveData.value!!.getOrThrow() | ||
val secondTopic = topicList.getTopicSummary(1) | ||
assertThat(secondTopic.topicId).isEqualTo(TEST_TOPIC_ID_1) | ||
assertThat(secondTopic.name).isEqualTo("Second Topic") | ||
} | ||
|
||
@Test | ||
fun testRetrieveTopicList_secondTopic_hasCorrectThumbnail() { | ||
val topicListLiveData = topicListController.getTopicList() | ||
|
||
val topicList = topicListLiveData.value!!.getOrThrow() | ||
val secondTopic = topicList.getTopicSummary(1) | ||
assertThat(secondTopic.topicThumbnail.thumbnailGraphic).isEqualTo(LessonThumbnailGraphic.CHILD_WITH_CUPCAKES) | ||
} | ||
|
||
@Test | ||
fun testRetrieveTopicList_secondTopic_hasCorrectLessonCount() { | ||
val topicListLiveData = topicListController.getTopicList() | ||
|
||
val topicList = topicListLiveData.value!!.getOrThrow() | ||
val secondTopic = topicList.getTopicSummary(1) | ||
assertThat(secondTopic.totalChapterCount).isEqualTo(1) | ||
} | ||
|
||
@Test | ||
fun testRetrieveTopicList_promotedLesson_hasCorrectLessonInfo() { | ||
val topicListLiveData = topicListController.getTopicList() | ||
|
||
val topicList = topicListLiveData.value!!.getOrThrow() | ||
val promotedStory = topicList.promotedStory | ||
assertThat(promotedStory.storyId).isEqualTo(TEST_STORY_ID_1) | ||
assertThat(promotedStory.storyName).isEqualTo("Second Story") | ||
} | ||
|
||
@Test | ||
fun testRetrieveTopicList_promotedLesson_hasCorrectTopicInfo() { | ||
val topicListLiveData = topicListController.getTopicList() | ||
|
||
val topicList = topicListLiveData.value!!.getOrThrow() | ||
val promotedStory = topicList.promotedStory | ||
assertThat(promotedStory.topicId).isEqualTo(TEST_TOPIC_ID_0) | ||
assertThat(promotedStory.topicName).isEqualTo("First Topic") | ||
} | ||
|
||
@Test | ||
fun testRetrieveTopicList_promotedLesson_hasCorrectCompletionStats() { | ||
val topicListLiveData = topicListController.getTopicList() | ||
|
||
val topicList = topicListLiveData.value!!.getOrThrow() | ||
val promotedStory = topicList.promotedStory | ||
assertThat(promotedStory.completedChapterCount).isEqualTo(1) | ||
assertThat(promotedStory.totalChapterCount).isEqualTo(3) | ||
} | ||
|
||
@Test | ||
fun testRetrieveTopicList_hasMultipleOngoingLessons() { | ||
val topicListLiveData = topicListController.getTopicList() | ||
|
||
val topicList = topicListLiveData.value!!.getOrThrow() | ||
assertThat(topicList.ongoingStoryCount).isEqualTo(2) | ||
} | ||
|
||
@Test | ||
fun testRetrieveOngoingStoryList_isSuccessful() { | ||
val ongoingStoryListLiveData = topicListController.getOngoingStoryList() | ||
|
||
val ongoingStoryListResult = ongoingStoryListLiveData.value | ||
assertThat(ongoingStoryListResult).isNotNull() | ||
assertThat(ongoingStoryListResult!!.isSuccess()).isTrue() | ||
} | ||
|
||
@Test | ||
fun testRetrieveOngoingStoryList_withinSevenDays_hasOngoingLesson() { | ||
val ongoingStoryListLiveData = topicListController.getOngoingStoryList() | ||
|
||
val ongoingStoryList = ongoingStoryListLiveData.value!!.getOrThrow() | ||
assertThat(ongoingStoryList.recentStoryCount).isEqualTo(1) | ||
} | ||
|
||
@Test | ||
fun testRetrieveOngoingStoryList_recentLesson_hasCorrectStoryInfo() { | ||
val ongoingStoryListLiveData = topicListController.getOngoingStoryList() | ||
|
||
val ongoingStoryList = ongoingStoryListLiveData.value!!.getOrThrow() | ||
val recentLesson = ongoingStoryList.getRecentStory(0) | ||
assertThat(recentLesson.storyId).isEqualTo(TEST_STORY_ID_1) | ||
assertThat(recentLesson.storyName).isEqualTo("Second Story") | ||
} | ||
|
||
@Test | ||
fun testRetrieveOngoingStoryList_recentLesson_hasCorrectTopicInfo() { | ||
val ongoingStoryListLiveData = topicListController.getOngoingStoryList() | ||
|
||
val ongoingStoryList = ongoingStoryListLiveData.value!!.getOrThrow() | ||
val recentLesson = ongoingStoryList.getRecentStory(0) | ||
assertThat(recentLesson.topicId).isEqualTo(TEST_TOPIC_ID_0) | ||
assertThat(recentLesson.topicName).isEqualTo("First Topic") | ||
} | ||
|
||
@Test | ||
fun testRetrieveOngoingStoryList_recentLesson_hasCorrectCompletionStats() { | ||
val ongoingStoryListLiveData = topicListController.getOngoingStoryList() | ||
|
||
val ongoingStoryList = ongoingStoryListLiveData.value!!.getOrThrow() | ||
val recentLesson = ongoingStoryList.getRecentStory(0) | ||
assertThat(recentLesson.completedChapterCount).isEqualTo(1) | ||
assertThat(recentLesson.totalChapterCount).isEqualTo(3) | ||
} | ||
|
||
@Test | ||
fun testRetrieveOngoingStoryList_recentLesson_hasCorrectThumbnail() { | ||
val ongoingStoryListLiveData = topicListController.getOngoingStoryList() | ||
|
||
val ongoingStoryList = ongoingStoryListLiveData.value!!.getOrThrow() | ||
val recentLesson = ongoingStoryList.getRecentStory(0) | ||
assertThat(recentLesson.lessonThumbnail.thumbnailGraphic).isEqualTo(LessonThumbnailGraphic.DUCK_AND_CHICKEN) | ||
} | ||
|
||
@Test | ||
fun testRetrieveOngoingStoryList_earlierThanSevenDays_doesNotHaveOngoingLesson() { | ||
val ongoingStoryListLiveData = topicListController.getOngoingStoryList() | ||
|
||
val ongoingStoryList = ongoingStoryListLiveData.value!!.getOrThrow() | ||
assertThat(ongoingStoryList.olderStoryCount).isEqualTo(0) | ||
} | ||
|
||
private fun setUpTestApplicationComponent() { | ||
DaggerTopicListControllerTest_TestApplicationComponent.builder() | ||
.setApplication(ApplicationProvider.getApplicationContext()) | ||
.build() | ||
.inject(this) | ||
} | ||
|
||
// TODO(#89): Move this to a common test application component. | ||
@Module | ||
class TestModule { | ||
@Provides | ||
@Singleton | ||
fun provideContext(application: Application): Context { | ||
return application | ||
} | ||
} | ||
|
||
// TODO(#89): Move this to a common test application component. | ||
@Singleton | ||
@Component(modules = [TestModule::class]) | ||
interface TestApplicationComponent { | ||
@Component.Builder | ||
interface Builder { | ||
@BindsInstance | ||
fun setApplication(application: Application): Builder | ||
|
||
fun build(): TestApplicationComponent | ||
} | ||
|
||
fun inject(topicListControllerTest: TopicListControllerTest) | ||
} | ||
} |
Oops, something went wrong.