Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #117: Introduce topic list controller interface [Blocked: #175] #176

Merged
merged 7 commits into from
Sep 25, 2019
117 changes: 117 additions & 0 deletions domain/src/main/java/org/oppia/domain/topic/TopicListController.kt
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()
}
}
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)
}
}
Loading