Skip to content

Commit f494eba

Browse files
Merge pull request #49 from LookUpGroup27/feature/quiz_play_screen
feat: Implement Quiz Play Screen and add CSV question files
2 parents 38b84ff + 3bd799f commit f494eba

File tree

11 files changed

+597
-39
lines changed

11 files changed

+597
-39
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ dependencies {
183183
androidTestImplementation(libs.mockito.android)
184184
androidTestImplementation(libs.mockito.kotlin)
185185
testImplementation(libs.robolectric)
186+
testImplementation("androidx.arch.core:core-testing:2.1.0")
186187

187188
// Kaspresso Allure
188189
androidTestImplementation(libs.kaspresso.allure.support)

app/src/androidTest/java/com/github/lookupgroup27/lookup/ui/quiz/QuizKtTest.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.compose.ui.test.junit4.createComposeRule
55
import androidx.compose.ui.test.onNodeWithTag
66
import androidx.compose.ui.test.onNodeWithText
77
import androidx.compose.ui.test.performClick
8+
import com.github.lookupgroup27.lookup.model.quiz.QuizViewModel
89
import com.github.lookupgroup27.lookup.ui.navigation.NavigationActions
910
import com.github.lookupgroup27.lookup.ui.navigation.Screen
1011
import org.junit.Rule
@@ -18,9 +19,12 @@ class QuizKtTest {
1819
@get:Rule val composeTestRule = createComposeRule()
1920

2021
private val mockNavigationActions: NavigationActions = mock()
22+
private val quizViewModel = QuizViewModel()
2123

2224
fun setUp() {
23-
composeTestRule.setContent { QuizScreen(navigationActions = mockNavigationActions) }
25+
composeTestRule.setContent {
26+
QuizScreen(quizViewModel, navigationActions = mockNavigationActions)
27+
}
2428
}
2529

2630
@Test
@@ -48,7 +52,7 @@ class QuizKtTest {
4852
composeTestRule.onNodeWithTag("go_back_button_quiz").performClick()
4953

5054
// Verify navigation back action is triggered
51-
verify(mockNavigationActions).goBack()
55+
verify(mockNavigationActions).navigateTo(Screen.MENU)
5256
}
5357

5458
@Test
Lines changed: 96 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,116 @@
1-
package com.github.lookupgroup27.lookup.ui.quiz
2-
3-
import androidx.compose.ui.test.assertIsDisplayed
1+
import androidx.compose.ui.test.*
42
import androidx.compose.ui.test.junit4.createComposeRule
5-
import androidx.compose.ui.test.onNodeWithTag
6-
import androidx.compose.ui.test.onNodeWithText
7-
import androidx.compose.ui.test.performClick
3+
import com.github.lookupgroup27.lookup.model.quiz.QuizQuestion
4+
import com.github.lookupgroup27.lookup.model.quiz.QuizViewModel
85
import com.github.lookupgroup27.lookup.ui.navigation.NavigationActions
6+
import com.github.lookupgroup27.lookup.ui.quiz.QuizPlayScreen
97
import org.junit.Before
108
import org.junit.Rule
119
import org.junit.Test
1210
import org.mockito.kotlin.mock
13-
import org.mockito.kotlin.verify
1411

1512
class QuizPlayKtTest {
16-
13+
val mockNavigationActions: NavigationActions = mock()
1714
@get:Rule val composeTestRule = createComposeRule()
1815

19-
private val mockNavigationActions: NavigationActions = mock()
16+
private lateinit var quizViewModel: QuizViewModel
2017

2118
@Before
22-
fun setUp() {
23-
composeTestRule.setContent { QuizPlayScreen(navigationActions = mockNavigationActions) }
19+
fun setup() {
20+
// Initialize the ViewModel with test data
21+
quizViewModel =
22+
QuizViewModel().apply {
23+
_quizQuestions.postValue(
24+
listOf(
25+
QuizQuestion(
26+
question = "In which galaxy is the solar system located?",
27+
answers =
28+
listOf(
29+
"The Milky Way",
30+
"The Great Spiral",
31+
"The Magellanic Cloud",
32+
"Andromeda"),
33+
correctAnswer = "The Milky Way")))
34+
}
2435
}
2536

2637
@Test
27-
fun quizPlayScreen_displaysCorrectly() {
28-
composeTestRule.onNodeWithText("Quiz Play Screen").assertIsDisplayed()
29-
composeTestRule.onNodeWithTag("go_back_button_quiz").assertIsDisplayed()
38+
fun testQuizPlayScreenDisplaysQuestionAndAnswers() {
39+
// Launch the QuizPlayScreen
40+
composeTestRule.setContent {
41+
QuizPlayScreen(viewModel = quizViewModel, navigationActions = mockNavigationActions)
42+
}
43+
44+
// Assert that the title is displayed
45+
composeTestRule.onNodeWithTag("quiz_title").assertTextEquals(" Quiz").assertIsDisplayed()
46+
47+
// Assert that the question is displayed
48+
composeTestRule
49+
.onNodeWithTag("quiz_question")
50+
.assertTextEquals("In which galaxy is the solar system located?")
51+
.assertIsDisplayed()
52+
53+
// Assert that all answer buttons are displayed
54+
composeTestRule
55+
.onNodeWithTag("answer_button_0")
56+
.assertTextEquals("The Milky Way")
57+
.assertIsDisplayed()
58+
composeTestRule
59+
.onNodeWithTag("answer_button_1")
60+
.assertTextEquals("The Great Spiral")
61+
.assertIsDisplayed()
62+
composeTestRule
63+
.onNodeWithTag("answer_button_2")
64+
.assertTextEquals("The Magellanic Cloud")
65+
.assertIsDisplayed()
66+
composeTestRule
67+
.onNodeWithTag("answer_button_3")
68+
.assertTextEquals("Andromeda")
69+
.assertIsDisplayed()
3070
}
3171

3272
@Test
33-
fun quizPlayScreen_clickBackButton_navigatesBack() {
34-
composeTestRule.onNodeWithTag("go_back_button_quiz").performClick()
35-
verify(mockNavigationActions).goBack()
73+
fun testAnswerSelectionEnablesNextButton() {
74+
// Launch the QuizPlayScreen
75+
composeTestRule.setContent {
76+
QuizPlayScreen(viewModel = quizViewModel, navigationActions = mockNavigationActions)
77+
}
78+
79+
// Verify that the "Next" button is not displayed initially
80+
composeTestRule.onNodeWithTag("next_button").assertDoesNotExist()
81+
82+
// Select an answer
83+
composeTestRule.onNodeWithTag("answer_button_0").performClick()
84+
85+
// Verify that the "Next" button is now displayed and enabled
86+
composeTestRule.onNodeWithTag("next_button").assertIsDisplayed().assertIsEnabled()
87+
}
88+
89+
@Test
90+
fun testNextButtonClickDisplaysScoreAndReturnButton() {
91+
// Launch the QuizPlayScreen
92+
composeTestRule.setContent {
93+
QuizPlayScreen(viewModel = quizViewModel, navigationActions = mockNavigationActions)
94+
}
95+
96+
// Select an answer to enable the "Next" button
97+
composeTestRule.onNodeWithTag("answer_button_0").performClick()
98+
composeTestRule.waitForIdle()
99+
100+
// Perform click on "Next" button
101+
composeTestRule.onNodeWithTag("next_button").performClick()
102+
composeTestRule.waitForIdle()
103+
104+
// Assert that the score text is displayed
105+
composeTestRule
106+
.onNodeWithTag("score_text")
107+
.assertIsDisplayed()
108+
.assertTextEquals("Your score: 1/1")
109+
110+
// Assert that the "Return to Quiz Selection" button is displayed
111+
composeTestRule
112+
.onNodeWithTag("return_to_quiz_selection_button")
113+
.assertIsDisplayed()
114+
.assertTextEquals("Return to Quiz Selection")
36115
}
37116
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
How did writer and Astrophysicist Carl Sagan nickname Earth?;The pale blue dot;The pale blue ball;The pale blue planet;The pale blue smurf
2+
Earth's orbit is...;an ellipse;a circle;a square;a hyperbola
3+
How many times smaller is the Moon than Earth?;~4x;~2x;~6x;~10x
4+
Earth's revolution period around the Sun is...;~365.25 days;~24 hours;~23 hours and 56 minutes;~30 days
5+
Earth's rotation period on itself is...;~23 hours and 56 minutes and 4 seconds;~24 hours;~365.25 days;~30 days
6+
A solar eclipse occurs when...;The Moon is positioned right in front of the Sun;The Moon passes in Earth's shadow;Sun falls asleep during the day;Clouds hide the sun
7+
What causes the seasons on Earth?;The tilt of the Earth's axis;The Earth’s rotation speed;The gravitational pull of the Moon;The distance from the Sun
8+
What is the shape of the Earth?;An oblate spheroid;A perfect sphere;A flat disc;A cube
9+
Galileo was put on trial in the 18th century because he claimed that...;The Earth revolves around the Sun;The Sun revolves around the Earth;The Earth does not rotate;The Earth is flat
10+
Io, Ganymede, Callisto, and Europa are the moons of...;Jupiter;Venus;Mars;Uranus
11+
A lunar eclipse occurs when...;The Moon passes into Earth's shadow;The Moon is positioned right in front of the Sun;The Earth hides behind the Sun;The Moon sets
12+
What is the origin of the Moon?;The Moon is the result of a collision between the proto-Earth and a proto-planet named Theia;The Moon is an asteroid that Earth captured with its gravity;The Moon is a small planet that fell into Earth's gravity;The Moon is a gift from Zeus to the young Gaia
13+
What is the diameter of the Earth?;~12,800 km;~6,400 km;~40,000 km;~400,000 km
14+
What is the Earth-Sun distance?;~150,000,000 km;~400,000 km;~40,000 km;~150,000,000,000 km
15+
What is the Earth-Moon distance?;~400,000 km;~150,000,000 km;~40,000 km;~12,800 km
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
In which galaxy is the solar system located?;The Milky Way;Andromeda;The Great Spiral;The Magellanic Cloud
2+
What is the Earth-Sun distance?;~150,000,000 km;~400,000 km;~40,000 km;~150,000,000,000 km
3+
Ptolemy, in the 2nd century BC, was convinced that the Solar System was...;geocentric (everything revolves around Earth);Heliocentric (everything revolves around the Sun);Mixed (the Sun and the Moon revolve around Earth, but planets revolve around the Sun);Racksocentric (everything revolves around money)
4+
Planet comes from the Greek term planḗtēs which means...;Wandering star;Small star;Bright star;Basketball
5+
The planet with the red spot is...;Jupiter;Mars;Mercury;Neptune
6+
The planet without a moon is...;Venus;Earth;Jupiter;Saturn
7+
The planet with rings is...;Saturn;Uranus;Neptune;Venus
8+
The demoted planet is...;Pluto;Earth;Uranus;Neptune
9+
The dwarf planet is...;Pluto;Uranus;Neptune;Mercury
10+
How many planets are there in the Solar System?;8;9;10;7
11+
Which planet has the most moons?;Saturn;Jupiter;Uranus;Mars
12+
What is the largest planet in the Solar System?;Jupiter;Uranus;Venus;Saturn
13+
Which planet is known as the Red Planet"?;Mars;Mercury;Neptune;Venus
14+
Which planet in the Solar System has the shortest day (rotation period)?;Jupiter;Earth;Mars;Venus
15+
Which planet is closest to the Sun?;Mercury;Venus;Earth;Mars

app/src/main/java/com/github/lookupgroup27/lookup/MainActivity.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import androidx.navigation.compose.composable
1313
import androidx.navigation.compose.navigation
1414
import androidx.navigation.compose.rememberNavController
1515
import com.github.lookupgroup27.lookup.model.calendar.CalendarViewModel
16+
import com.github.lookupgroup27.lookup.model.quiz.QuizViewModel
1617
import com.github.lookupgroup27.lookup.ui.authentication.SignInScreen
1718
import com.github.lookupgroup27.lookup.ui.calendar.CalendarScreen
1819
import com.github.lookupgroup27.lookup.ui.map.MapScreen
@@ -48,6 +49,7 @@ fun LookUpApp() {
4849
val navController = rememberNavController()
4950
val navigationActions = NavigationActions(navController)
5051
val calendarViewModel: CalendarViewModel = viewModel(factory = CalendarViewModel.Factory)
52+
val quizViewModel: QuizViewModel = viewModel()
5153

5254
NavHost(navController = navController, startDestination = Route.LANDING) {
5355
navigation(
@@ -75,12 +77,12 @@ fun LookUpApp() {
7577
composable(Screen.PROFILE) { ProfileScreen(navigationActions) }
7678
composable(Screen.CALENDAR) { CalendarScreen(calendarViewModel, navigationActions) }
7779
composable(Screen.SKY_TRACKER) { SkyTrackerScreen(navigationActions) }
78-
composable(Screen.QUIZ) { QuizScreen(navigationActions) }
80+
composable(Screen.QUIZ) { QuizScreen(quizViewModel, navigationActions) }
7981
}
8082

8183
navigation(startDestination = Screen.QUIZ, route = Route.QUIZ) {
82-
composable(Screen.QUIZ) { QuizScreen(navigationActions) }
83-
composable(Screen.QUIZ_PLAY) { QuizPlayScreen(navigationActions) }
84+
composable(Screen.QUIZ) { QuizScreen(quizViewModel, navigationActions) }
85+
composable(Screen.QUIZ_PLAY) { QuizPlayScreen(quizViewModel, navigationActions) }
8486
}
8587

8688
navigation(startDestination = Screen.PROFILE, route = Route.PROFILE) {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.github.lookupgroup27.lookup.model.quiz
2+
3+
data class QuizQuestion(val question: String, val answers: List<String>, val correctAnswer: String)
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package com.github.lookupgroup27.lookup.model.quiz
2+
3+
import android.content.Context
4+
import androidx.annotation.VisibleForTesting
5+
import androidx.lifecycle.LiveData
6+
import androidx.lifecycle.MutableLiveData
7+
import androidx.lifecycle.ViewModel
8+
import java.io.InputStreamReader
9+
10+
open class QuizViewModel : ViewModel() {
11+
12+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
13+
val _quizQuestions = MutableLiveData<List<QuizQuestion>>()
14+
val quizQuestions: LiveData<List<QuizQuestion>> = _quizQuestions
15+
16+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
17+
var _currentQuestionIndex = MutableLiveData(0)
18+
val currentQuestionIndex: LiveData<Int> = _currentQuestionIndex
19+
20+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) var _score = MutableLiveData(0)
21+
val score: LiveData<Int> = _score
22+
23+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) var _showScore = MutableLiveData(false)
24+
val showScore: LiveData<Boolean> = _showScore
25+
26+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
27+
var _selectedAnswer = MutableLiveData<String?>()
28+
val selectedAnswer: LiveData<String?> = _selectedAnswer
29+
30+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
31+
val _quizTitle = MutableLiveData<String>()
32+
val quizTitle: LiveData<String> = _quizTitle
33+
34+
private val quizFolder = "quizzes/"
35+
36+
fun loadQuizDataForTheme(theme: String, context: Context) {
37+
val filePath = getFilePathForTheme(theme)
38+
val questions = loadCsvData(filePath, context)
39+
_quizQuestions.value = questions
40+
_currentQuestionIndex.value = 0
41+
_score.value = 0
42+
_showScore.value = false
43+
_quizTitle.value = theme
44+
}
45+
46+
private fun getFilePathForTheme(theme: String): String {
47+
return when (theme) {
48+
"Solar System" -> "${quizFolder}solar_system_quiz.csv"
49+
"Earth" -> "${quizFolder}earth_quiz.csv"
50+
else -> "${quizFolder}default_quiz.csv"
51+
}
52+
}
53+
54+
private fun loadCsvData(filePath: String, context: Context): List<QuizQuestion> {
55+
val questions = mutableListOf<QuizQuestion>()
56+
57+
try {
58+
val inputStream = context.assets.open(filePath)
59+
val reader = InputStreamReader(inputStream)
60+
61+
reader.useLines { lines ->
62+
lines.forEach { line ->
63+
val tokens = line.split(";")
64+
if (tokens.size == 5) {
65+
val questionText = tokens[0]
66+
val correctAnswer = tokens[1]
67+
val wrongAnswers = listOf(tokens[2], tokens[3], tokens[4])
68+
val shuffledAnswers = (wrongAnswers + correctAnswer).shuffled()
69+
questions.add(QuizQuestion(questionText, shuffledAnswers, correctAnswer))
70+
}
71+
}
72+
}
73+
} catch (e: Exception) {
74+
e.printStackTrace()
75+
}
76+
77+
return questions
78+
}
79+
80+
fun onAnswerSelected(answer: String) {
81+
_selectedAnswer.value = answer
82+
}
83+
84+
fun goToNextQuestion() {
85+
if (_selectedAnswer.value ==
86+
_quizQuestions.value?.get(_currentQuestionIndex.value ?: 0)?.correctAnswer) {
87+
_score.value = _score.value?.plus(1)
88+
}
89+
90+
if ((_currentQuestionIndex.value ?: 0) == (_quizQuestions.value?.size ?: 0) - 1) {
91+
_showScore.value = true
92+
} else {
93+
_currentQuestionIndex.value = (_currentQuestionIndex.value ?: 0) + 1
94+
_selectedAnswer.value = null
95+
}
96+
}
97+
98+
fun resetQuiz() {
99+
_currentQuestionIndex.value = 0
100+
_score.value = 0
101+
_showScore.value = false
102+
_selectedAnswer.value = null
103+
_quizQuestions.value = emptyList()
104+
}
105+
}

0 commit comments

Comments
 (0)