Skip to content

Commit 067494f

Browse files
Merge pull request #29 from SwEnt-Group13/feature/event-listing
Feature/event listing - Implement Event List Overview with Mock Data, UI Enhancements, and Unit Tests
2 parents 43b5e4d + d35b4dd commit 067494f

File tree

13 files changed

+780
-1
lines changed

13 files changed

+780
-1
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package com.android.unio.ui.events
2+
3+
import androidx.compose.ui.test.*
4+
import androidx.compose.ui.test.junit4.createComposeRule
5+
import androidx.compose.ui.unit.ExperimentalUnitApi
6+
import androidx.test.ext.junit.runners.AndroidJUnit4
7+
import com.android.unio.model.event.Event
8+
import com.android.unio.model.event.EventListViewModel
9+
import com.android.unio.model.event.EventRepositoryMock
10+
import com.android.unio.ui.event.EventListOverview
11+
import kotlinx.coroutines.test.runBlockingTest
12+
import org.junit.Rule
13+
import org.junit.Test
14+
import org.junit.runner.RunWith
15+
16+
/**
17+
* Test class for the EventListOverview Composable. This class contains unit tests to validate the
18+
* behavior of the Event List UI.
19+
*/
20+
@ExperimentalUnitApi
21+
@RunWith(AndroidJUnit4::class)
22+
class EventListOverviewTest {
23+
24+
@get:Rule val composeTestRule = createComposeRule()
25+
26+
// Mock event repository to provide test data.
27+
private val mockEventRepository = EventRepositoryMock()
28+
29+
/**
30+
* Tests the functionality of switching between tabs and verifying animations. Ensures that the
31+
* 'All' tab exists and can be clicked, and verifies the underlying bar's presence when switching
32+
* tabs.
33+
*/
34+
@Test
35+
fun testTabSwitchingAndAnimation() {
36+
composeTestRule.setContent {
37+
val eventListViewModel = EventListViewModel(mockEventRepository)
38+
EventListOverview(eventListViewModel = eventListViewModel, onAddEvent = {}, onEventClick = {})
39+
}
40+
41+
// Assert that the 'All' tab exists and has a click action.
42+
composeTestRule.onNodeWithTag("event_tabAll").assertExists()
43+
composeTestRule.onNodeWithTag("event_tabAll").assertHasClickAction()
44+
45+
// Assert that the underlying bar exists.
46+
composeTestRule.onNodeWithTag("event_UnderlyingBar").assertExists()
47+
48+
// Perform a click on the 'Following' tab.
49+
composeTestRule.onNodeWithTag("event_tabFollowing").performClick()
50+
51+
// Assert that the 'Following' tab and the underlying bar still exist.
52+
composeTestRule.onNodeWithTag("event_tabFollowing").assertExists()
53+
composeTestRule.onNodeWithTag("event_UnderlyingBar").assertExists()
54+
}
55+
56+
/**
57+
* Tests the UI when the event list is empty. Asserts that the appropriate message is displayed
58+
* when there are no events available.
59+
*/
60+
@Test
61+
fun testEmptyEventList() {
62+
composeTestRule.setContent {
63+
val emptyEventRepository =
64+
object : EventRepositoryMock() {
65+
override fun getEvents(
66+
onSuccess: (List<Event>) -> Unit,
67+
onFailure: (Exception) -> Unit
68+
) {
69+
// Return an empty list for testing
70+
onSuccess(emptyList())
71+
}
72+
}
73+
val eventListViewModel = EventListViewModel(emptyEventRepository)
74+
EventListOverview(eventListViewModel = eventListViewModel, onAddEvent = {}, onEventClick = {})
75+
}
76+
77+
// Assert that the empty event prompt is displayed.
78+
composeTestRule.onNodeWithTag("event_emptyEventPrompt").assertExists()
79+
composeTestRule.onNodeWithText("No events available.").assertExists()
80+
}
81+
82+
/**
83+
* Tests the functionality of the Map button. Verifies that clicking the button triggers the
84+
* expected action.
85+
*/
86+
@Test
87+
fun testMapButton() {
88+
var mapClicked = false
89+
90+
composeTestRule.setContent {
91+
val eventListViewModel = EventListViewModel(mockEventRepository)
92+
EventListOverview(
93+
eventListViewModel = eventListViewModel,
94+
onAddEvent = { mapClicked = true },
95+
onEventClick = {})
96+
}
97+
98+
composeTestRule.onNodeWithTag("event_MapButton").performClick()
99+
100+
assert(mapClicked)
101+
}
102+
103+
/**
104+
* Tests the sequence of clicking on the 'Following' tab and then on the 'Add' button to ensure
105+
* that both actions trigger their respective animations and behaviors.
106+
*/
107+
@Test
108+
fun testClickFollowingAndAdd() = runBlockingTest {
109+
var addClicked = false
110+
111+
composeTestRule.setContent {
112+
val eventListViewModel = EventListViewModel(mockEventRepository)
113+
EventListOverview(
114+
eventListViewModel = eventListViewModel,
115+
onAddEvent = { addClicked = true },
116+
onEventClick = {})
117+
}
118+
119+
// Ensure the 'Following' tab exists and perform a click.
120+
composeTestRule.onNodeWithTag("event_tabFollowing").assertExists()
121+
composeTestRule.onNodeWithTag("event_tabFollowing").performClick()
122+
123+
// Perform a click on the 'Add' button.
124+
composeTestRule.onNodeWithTag("event_MapButton").assertExists()
125+
composeTestRule.onNodeWithTag("event_MapButton").performClick()
126+
127+
// Assert that the 'Add' button was clicked.
128+
assert(addClicked)
129+
130+
// Optionally, verify that the animation related to the 'Add' button was triggered.
131+
// This could involve checking the state changes or specific UI elements.
132+
}
133+
}

app/src/main/java/com/android/unio/model/event/Event.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ data class Event(
1717
val price: Double = 0.0,
1818
val date: Timestamp = Timestamp(Date()),
1919
val location: Location = Location(),
20-
val types: List<String> = mutableListOf()
20+
val types: List<EventType> = mutableListOf<EventType>()
2121
)
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.android.unio.model.event
2+
3+
import androidx.lifecycle.ViewModel
4+
import androidx.lifecycle.ViewModelProvider
5+
import androidx.lifecycle.viewModelScope
6+
import kotlinx.coroutines.flow.MutableStateFlow
7+
import kotlinx.coroutines.flow.StateFlow
8+
import kotlinx.coroutines.launch
9+
10+
/**
11+
* ViewModel class that manages the event list data and provides it to the UI. It uses an
12+
* [EventRepository] to load the list of events and exposes them through a [StateFlow] to be
13+
* observed by the UI.
14+
*
15+
* @property repository The [EventRepository] that provides the events.
16+
*/
17+
class EventListViewModel(private val repository: EventRepository) : ViewModel() {
18+
19+
/**
20+
* A private mutable state flow that holds the list of events. It is internal to the ViewModel and
21+
* cannot be modified from the outside.
22+
*/
23+
private val _events = MutableStateFlow<List<Event>>(emptyList())
24+
25+
/**
26+
* A public immutable [StateFlow] that exposes the list of events to the UI. This flow can only be
27+
* observed and not modified.
28+
*/
29+
val events: StateFlow<List<Event>> = _events
30+
31+
/** Initializes the ViewModel by loading the events from the repository. */
32+
init {
33+
loadEvents()
34+
}
35+
36+
/**
37+
* Loads the list of events from the repository asynchronously using coroutines and updates the
38+
* internal [MutableStateFlow].
39+
*/
40+
private fun loadEvents() {
41+
// Launch a coroutine in the ViewModel scope to load events asynchronously
42+
viewModelScope.launch {
43+
repository.getEvents(
44+
onSuccess = { eventList ->
45+
_events.value = eventList // Update the state flow with the loaded events
46+
},
47+
onFailure = { exception ->
48+
// Handle error (e.g., log it, show a message to the user)
49+
_events.value = emptyList() // Clear events on failure or handle accordingly
50+
})
51+
}
52+
}
53+
54+
/**
55+
* Companion object that provides a factory for creating instances of [EventListViewModel]. This
56+
* factory is used to create the ViewModel with the [EventRepositoryMock] dependency.
57+
*/
58+
companion object {
59+
/** A factory for creating [EventListViewModel] instances with the [EventRepositoryMock]. */
60+
val Factory: ViewModelProvider.Factory =
61+
object : ViewModelProvider.Factory {
62+
/**
63+
* Creates an instance of the [EventListViewModel].
64+
*
65+
* @param modelClass The class of the ViewModel to create.
66+
* @return The created ViewModel instance.
67+
* @throws IllegalArgumentException if the [modelClass] does not match.
68+
*/
69+
@Suppress("UNCHECKED_CAST")
70+
override fun <T : ViewModel> create(modelClass: Class<T>): T {
71+
return EventListViewModel(EventRepositoryMock()) as T
72+
}
73+
}
74+
}
75+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
package com.android.unio.model.event
2+
3+
import com.android.unio.model.association.Association
4+
import com.android.unio.model.firestore.MockReferenceList
5+
import com.android.unio.model.map.Location
6+
import com.google.firebase.Timestamp
7+
import java.util.Date
8+
import java.util.UUID
9+
10+
/**
11+
* A mock implementation of the EventRepository interface. This class is used for testing purposes
12+
* to provide a predefined list of events.
13+
*
14+
* Since the actual EventRepository is not implemented yet, this mock repository allows for easy
15+
* testing with specific data.
16+
*/
17+
open class EventRepositoryMock : EventRepository {
18+
19+
/**
20+
* Retrieves a list of mock events.
21+
*
22+
* @return A list of [Event] objects with predefined data for testing.
23+
*/
24+
override fun getEvents(onSuccess: (List<Event>) -> Unit, onFailure: (Exception) -> Unit) {
25+
try {
26+
val events =
27+
listOf(
28+
Event(
29+
uid = UUID.randomUUID().toString(),
30+
title = "WESKIC",
31+
organisers = MockReferenceList<Association>(),
32+
taggedAssociations = MockReferenceList<Association>(),
33+
image = "weskic",
34+
description =
35+
"The Summer Festival features live music, food stalls, and various activities for all ages.",
36+
catchyDescription = "Come to the best event of the Coaching IC!",
37+
price = 0.0,
38+
date = Timestamp(Date(2024 - 1900, 6, 20)), // July 20, 2024
39+
location = Location(0.0, 0.0, "USA"),
40+
types = listOf(EventType.TRIP)),
41+
Event(
42+
uid = UUID.randomUUID().toString(),
43+
title = "Oktoberweek",
44+
organisers = MockReferenceList<Association>(),
45+
taggedAssociations = MockReferenceList<Association>(),
46+
image = "oktoberweek",
47+
description =
48+
"An evening of networking with industry leaders and innovators. Don't miss out!",
49+
catchyDescription = "There never enough beersssssss!",
50+
price = 10.0,
51+
date = Timestamp(Date(2024 - 1900, 4, 15)), // May 15, 2024
52+
location = Location(1.0, 1.0, "USA"),
53+
types = listOf(EventType.OTHER)),
54+
Event(
55+
uid = UUID.randomUUID().toString(),
56+
title = "SwissTech Talk",
57+
organisers = MockReferenceList<Association>(),
58+
taggedAssociations = MockReferenceList<Association>(),
59+
image = "swisstechtalk",
60+
description =
61+
"Learn Kotlin from scratch with real-world examples and expert guidance.",
62+
catchyDescription = "Don't miss the chant de section!",
63+
price = 0.0,
64+
date = Timestamp(Date(2024 - 1900, 2, 10)), // March 10, 2024
65+
location = Location(2.0, 2.0, "USA"),
66+
types = listOf(EventType.OTHER)),
67+
Event(
68+
uid = UUID.randomUUID().toString(),
69+
title = "Lapin Vert",
70+
organisers = MockReferenceList<Association>(),
71+
taggedAssociations = MockReferenceList<Association>(),
72+
image = "lapin_vert",
73+
description =
74+
"Join us for an unforgettable evening featuring local artists and musicians.",
75+
catchyDescription = "Venez, il y a des gens sympa!",
76+
price = 0.0,
77+
date = Timestamp(Date(2024 - 1900, 8, 25)), // September 25, 2024
78+
location = Location(3.0, 3.0, "USA"),
79+
types = listOf(EventType.OTHER)),
80+
Event(
81+
uid = UUID.randomUUID().toString(),
82+
title = "Choose your coach!",
83+
organisers = MockReferenceList<Association>(),
84+
taggedAssociations = MockReferenceList<Association>(),
85+
image = "chooseyourcoach",
86+
description =
87+
"Participate in various sports activities and enjoy food and entertainment.",
88+
catchyDescription = "Pick the best one!",
89+
price = 5.0,
90+
date = Timestamp(Date(2024 - 1900, 5, 5)), // June 5, 2024
91+
location = Location(4.0, 4.0, "USA"),
92+
types = listOf(EventType.SPORT)),
93+
Event(
94+
uid = UUID.randomUUID().toString(),
95+
title = "Concert",
96+
organisers = MockReferenceList<Association>(),
97+
taggedAssociations = MockReferenceList<Association>(),
98+
image = "antoinoxlephar",
99+
description =
100+
"A workshop dedicated to teaching strategies for successful social media marketing.",
101+
catchyDescription = "Best concert everrrrr!",
102+
price = 15.0,
103+
date = Timestamp(Date(2024 - 1900, 7, 30)), // August 30, 2024
104+
location = Location(5.0, 5.0, "USA"),
105+
types = listOf(EventType.OTHER)),
106+
Event(
107+
uid = UUID.randomUUID().toString(),
108+
title = "Jam Session: Local Artists",
109+
organisers = MockReferenceList<Association>(),
110+
taggedAssociations = MockReferenceList<Association>(),
111+
image = "photo_2024_10_08_14_57_48",
112+
description =
113+
"An evening of music with local artists. Bring your instruments or just enjoy the show!",
114+
catchyDescription = "Support local talent in this open jam session!",
115+
price = 0.0,
116+
date = Timestamp(Date(2024 - 1900, 3, 12)), // April 12, 2024
117+
location = Location(6.0, 6.0, "USA"),
118+
types = listOf(EventType.JAM)))
119+
onSuccess(events)
120+
} catch (e: Exception) {
121+
onFailure(e)
122+
}
123+
}
124+
125+
// Mock implementation for getting events by association
126+
override fun getEventsOfAssociation(
127+
association: String,
128+
onSuccess: (List<Event>) -> Unit,
129+
onFailure: (Exception) -> Unit
130+
) {
131+
// Filter mock events by tagged associations
132+
getEvents(
133+
{ events ->
134+
onSuccess(events.filter { it.taggedAssociations.list.value.isEmpty() })
135+
}, // Now filtering for empty tagged associations
136+
onFailure)
137+
}
138+
139+
// Mock implementation for getting events between two dates
140+
override fun getNextEventsFromDateToDate(
141+
startDate: Timestamp,
142+
endDate: Timestamp,
143+
onSuccess: (List<Event>) -> Unit,
144+
onFailure: (Exception) -> Unit
145+
) {
146+
// Filter mock events by date range
147+
getEvents(
148+
{ events -> onSuccess(events.filter { it.date >= startDate && it.date <= endDate }) },
149+
onFailure)
150+
}
151+
152+
// Mock implementation to generate a new UID
153+
override fun getNewUid(): String {
154+
return UUID.randomUUID().toString()
155+
}
156+
157+
// Mock implementation to add an event
158+
override fun addEvent(event: Event, onSuccess: () -> Unit, onFailure: (Exception) -> Unit) {
159+
// This is a mock, so we assume the event is added successfully
160+
onSuccess()
161+
}
162+
163+
// Mock implementation to delete an event by ID
164+
override fun deleteEventById(id: String, onSuccess: () -> Unit, onFailure: (Exception) -> Unit) {
165+
// This is a mock, so we assume the event is deleted successfully
166+
onSuccess()
167+
}
168+
}

0 commit comments

Comments
 (0)