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

Feature/event listing - Implement Event List Overview with Mock Data, UI Enhancements, and Unit Tests #29

Merged
merged 31 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d097d0f
feat(model/events): add Event data class and Firestore repository
Aurelien9Code Oct 6, 2024
6b9020d
feat(events): add mock implementation of EventRepository
Aurelien9Code Oct 7, 2024
2cef11b
feat(events): add EventListViewModel with mock data loading
Aurelien9Code Oct 7, 2024
71f6ebf
feat(events): Create & Enhance Event List Overview with tab selection…
Aurelien9Code Oct 7, 2024
40d8101
feat(events): Create & Enhance Event List Overview with tab selection…
Aurelien9Code Oct 7, 2024
dd2de2b
feat(events): add event type colors mapping and retrieval function
Aurelien9Code Oct 8, 2024
448d0a5
feat(events): add catchy_description, main_type, and picture properti…
Aurelien9Code Oct 8, 2024
80045f1
feat(events): add some example types
Aurelien9Code Oct 8, 2024
952f958
feat(events): minor change to the event type - color mapping
Aurelien9Code Oct 8, 2024
158988b
feat(events): add EventListOverview preview and enhance EventItem UI
Aurelien9Code Oct 8, 2024
8c49a6c
feat(events): add comments using Java Doc conventional comments
Aurelien9Code Oct 8, 2024
b87a9ee
test(events): add unit tests for EventListOverview component
Aurelien9Code Oct 8, 2024
c79e00b
feat(events): add test tags for UI components
Aurelien9Code Oct 8, 2024
d5bc008
Merge branch 'main' into feature/event-listing
Aurelien9Code Oct 8, 2024
5d9d633
feat(events): execute ktfmtFormat
Aurelien9Code Oct 8, 2024
7d5fe28
feat(events): add pictures for temporary uses
Aurelien9Code Oct 8, 2024
e4b4332
feat(events): remove weird test temporarily
Aurelien9Code Oct 8, 2024
2479a35
style(Kotlin Format) : format file using ktfmt
Aurelien9Code Oct 8, 2024
c274b20
test(events) : remove bugged test
Aurelien9Code Oct 9, 2024
aaf4288
style(Kotlin Format) : format file using ktfmt
Aurelien9Code Oct 9, 2024
5ea51c5
test(events) : add a new test
Aurelien9Code Oct 9, 2024
3d12e82
feat(events) : change types in english for consistency
Aurelien9Code Oct 10, 2024
826dec1
feat(events) : modified EventTypeColor into EventType and into an enu…
Aurelien9Code Oct 10, 2024
f688e0a
feat(events) : modified all files according to the new EventType.kt s…
Aurelien9Code Oct 10, 2024
2666739
feat(events) : modify structure of Event.kt for camelCase convention
Aurelien9Code Oct 10, 2024
93abbeb
feat(events) : deleted unused directory
Aurelien9Code Oct 10, 2024
3c3ace8
refactor(viewmodel): handle asynchronous event loading with coroutines
Aurelien9Code Oct 10, 2024
c1ac946
style(format): format file using ktfmt format
Aurelien9Code Oct 10, 2024
b5fd44b
feat(events) : use real EventRepository structure and delete test one…
Aurelien9Code Oct 10, 2024
07362b4
Merge branch 'main' into feature/event-listing
Aurelien9Code Oct 10, 2024
d35b4dd
feat(events) : modify mock file according to conflict resolution
Aurelien9Code Oct 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package com.android.unio.ui.events

import androidx.compose.ui.test.*
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.unit.ExperimentalUnitApi
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.unio.model.event.Event
import com.android.unio.model.event.EventListViewModel
import com.android.unio.model.event.EventRepositoryMock
import com.android.unio.ui.event.EventListOverview
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

/**
* Test class for the EventListOverview Composable. This class contains unit tests to validate the
* behavior of the Event List UI.
*/
@ExperimentalUnitApi
@RunWith(AndroidJUnit4::class)
class EventListOverviewTest {

@get:Rule val composeTestRule = createComposeRule()

// Mock event repository to provide test data.
private val mockEventRepository = EventRepositoryMock()

/**
* Tests the functionality of switching between tabs and verifying animations. Ensures that the
* 'All' tab exists and can be clicked, and verifies the underlying bar's presence when switching
* tabs.
*/
@Test
fun testTabSwitchingAndAnimation() {
composeTestRule.setContent {
val eventListViewModel = EventListViewModel(mockEventRepository)
EventListOverview(eventListViewModel = eventListViewModel, onAddEvent = {}, onEventClick = {})
}

// Assert that the 'All' tab exists and has a click action.
composeTestRule.onNodeWithTag("event_tabAll").assertExists()
composeTestRule.onNodeWithTag("event_tabAll").assertHasClickAction()

// Assert that the underlying bar exists.
composeTestRule.onNodeWithTag("event_UnderlyingBar").assertExists()

// Perform a click on the 'Following' tab.
composeTestRule.onNodeWithTag("event_tabFollowing").performClick()

// Assert that the 'Following' tab and the underlying bar still exist.
composeTestRule.onNodeWithTag("event_tabFollowing").assertExists()
composeTestRule.onNodeWithTag("event_UnderlyingBar").assertExists()
}

/**
* Tests the UI when the event list is empty. Asserts that the appropriate message is displayed
* when there are no events available.
*/
@Test
fun testEmptyEventList() {
composeTestRule.setContent {
val emptyEventRepository =
object : EventRepositoryMock() {
override fun getEvents(
onSuccess: (List<Event>) -> Unit,
onFailure: (Exception) -> Unit
) {
// Return an empty list for testing
onSuccess(emptyList())
}
}
val eventListViewModel = EventListViewModel(emptyEventRepository)
EventListOverview(eventListViewModel = eventListViewModel, onAddEvent = {}, onEventClick = {})
}

// Assert that the empty event prompt is displayed.
composeTestRule.onNodeWithTag("event_emptyEventPrompt").assertExists()
composeTestRule.onNodeWithText("No events available.").assertExists()
}

/**
* Tests the functionality of the Map button. Verifies that clicking the button triggers the
* expected action.
*/
@Test
fun testMapButton() {
var mapClicked = false

composeTestRule.setContent {
val eventListViewModel = EventListViewModel(mockEventRepository)
EventListOverview(
eventListViewModel = eventListViewModel,
onAddEvent = { mapClicked = true },
onEventClick = {})
}

composeTestRule.onNodeWithTag("event_MapButton").performClick()

assert(mapClicked)
}

/**
* Tests the sequence of clicking on the 'Following' tab and then on the 'Add' button to ensure
* that both actions trigger their respective animations and behaviors.
*/
@Test
fun testClickFollowingAndAdd() = runBlockingTest {
var addClicked = false

composeTestRule.setContent {
val eventListViewModel = EventListViewModel(mockEventRepository)
EventListOverview(
eventListViewModel = eventListViewModel,
onAddEvent = { addClicked = true },
onEventClick = {})
}

// Ensure the 'Following' tab exists and perform a click.
composeTestRule.onNodeWithTag("event_tabFollowing").assertExists()
composeTestRule.onNodeWithTag("event_tabFollowing").performClick()

// Perform a click on the 'Add' button.
composeTestRule.onNodeWithTag("event_MapButton").assertExists()
composeTestRule.onNodeWithTag("event_MapButton").performClick()

// Assert that the 'Add' button was clicked.
assert(addClicked)

// Optionally, verify that the animation related to the 'Add' button was triggered.
// This could involve checking the state changes or specific UI elements.
}
}
2 changes: 1 addition & 1 deletion app/src/main/java/com/android/unio/model/event/Event.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ data class Event(
val price: Double = 0.0,
val date: Timestamp = Timestamp(Date()),
val location: Location = Location(),
val types: List<String> = mutableListOf()
val types: List<EventType> = mutableListOf<EventType>()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.android.unio.model.event

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

/**
* ViewModel class that manages the event list data and provides it to the UI. It uses an
* [EventRepository] to load the list of events and exposes them through a [StateFlow] to be
* observed by the UI.
*
* @property repository The [EventRepository] that provides the events.
*/
class EventListViewModel(private val repository: EventRepository) : ViewModel() {

/**
* A private mutable state flow that holds the list of events. It is internal to the ViewModel and
* cannot be modified from the outside.
*/
private val _events = MutableStateFlow<List<Event>>(emptyList())

/**
* A public immutable [StateFlow] that exposes the list of events to the UI. This flow can only be
* observed and not modified.
*/
val events: StateFlow<List<Event>> = _events

/** Initializes the ViewModel by loading the events from the repository. */
init {
loadEvents()
}

/**
* Loads the list of events from the repository asynchronously using coroutines and updates the
* internal [MutableStateFlow].
*/
private fun loadEvents() {
// Launch a coroutine in the ViewModel scope to load events asynchronously
viewModelScope.launch {
repository.getEvents(
onSuccess = { eventList ->
_events.value = eventList // Update the state flow with the loaded events
},
onFailure = { exception ->
// Handle error (e.g., log it, show a message to the user)
_events.value = emptyList() // Clear events on failure or handle accordingly
})
}
}

/**
* Companion object that provides a factory for creating instances of [EventListViewModel]. This
* factory is used to create the ViewModel with the [EventRepositoryMock] dependency.
*/
companion object {
/** A factory for creating [EventListViewModel] instances with the [EventRepositoryMock]. */
val Factory: ViewModelProvider.Factory =
object : ViewModelProvider.Factory {
/**
* Creates an instance of the [EventListViewModel].
*
* @param modelClass The class of the ViewModel to create.
* @return The created ViewModel instance.
* @throws IllegalArgumentException if the [modelClass] does not match.
*/
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return EventListViewModel(EventRepositoryMock()) as T
}
}
}
}
168 changes: 168 additions & 0 deletions app/src/main/java/com/android/unio/model/event/EventRepositoryMock.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package com.android.unio.model.event

import com.android.unio.model.association.Association
import com.android.unio.model.firestore.MockReferenceList
import com.android.unio.model.map.Location
import com.google.firebase.Timestamp
import java.util.Date
import java.util.UUID

/**
* A mock implementation of the EventRepository interface. This class is used for testing purposes
* to provide a predefined list of events.
*
* Since the actual EventRepository is not implemented yet, this mock repository allows for easy
* testing with specific data.
*/
open class EventRepositoryMock : EventRepository {

/**
* Retrieves a list of mock events.
*
* @return A list of [Event] objects with predefined data for testing.
*/
override fun getEvents(onSuccess: (List<Event>) -> Unit, onFailure: (Exception) -> Unit) {
try {
val events =
listOf(
Event(
uid = UUID.randomUUID().toString(),
title = "WESKIC",
organisers = MockReferenceList<Association>(),
taggedAssociations = MockReferenceList<Association>(),
image = "weskic",
description =
"The Summer Festival features live music, food stalls, and various activities for all ages.",
catchyDescription = "Come to the best event of the Coaching IC!",
price = 0.0,
date = Timestamp(Date(2024 - 1900, 6, 20)), // July 20, 2024
location = Location(0.0, 0.0, "USA"),
types = listOf(EventType.TRIP)),
Event(
uid = UUID.randomUUID().toString(),
title = "Oktoberweek",
organisers = MockReferenceList<Association>(),
taggedAssociations = MockReferenceList<Association>(),
image = "oktoberweek",
description =
"An evening of networking with industry leaders and innovators. Don't miss out!",
catchyDescription = "There never enough beersssssss!",
price = 10.0,
date = Timestamp(Date(2024 - 1900, 4, 15)), // May 15, 2024
location = Location(1.0, 1.0, "USA"),
types = listOf(EventType.OTHER)),
Event(
uid = UUID.randomUUID().toString(),
title = "SwissTech Talk",
organisers = MockReferenceList<Association>(),
taggedAssociations = MockReferenceList<Association>(),
image = "swisstechtalk",
description =
"Learn Kotlin from scratch with real-world examples and expert guidance.",
catchyDescription = "Don't miss the chant de section!",
price = 0.0,
date = Timestamp(Date(2024 - 1900, 2, 10)), // March 10, 2024
location = Location(2.0, 2.0, "USA"),
types = listOf(EventType.OTHER)),
Event(
uid = UUID.randomUUID().toString(),
title = "Lapin Vert",
organisers = MockReferenceList<Association>(),
taggedAssociations = MockReferenceList<Association>(),
image = "lapin_vert",
description =
"Join us for an unforgettable evening featuring local artists and musicians.",
catchyDescription = "Venez, il y a des gens sympa!",
price = 0.0,
date = Timestamp(Date(2024 - 1900, 8, 25)), // September 25, 2024
location = Location(3.0, 3.0, "USA"),
types = listOf(EventType.OTHER)),
Event(
uid = UUID.randomUUID().toString(),
title = "Choose your coach!",
organisers = MockReferenceList<Association>(),
taggedAssociations = MockReferenceList<Association>(),
image = "chooseyourcoach",
description =
"Participate in various sports activities and enjoy food and entertainment.",
catchyDescription = "Pick the best one!",
price = 5.0,
date = Timestamp(Date(2024 - 1900, 5, 5)), // June 5, 2024
location = Location(4.0, 4.0, "USA"),
types = listOf(EventType.SPORT)),
Event(
uid = UUID.randomUUID().toString(),
title = "Concert",
organisers = MockReferenceList<Association>(),
taggedAssociations = MockReferenceList<Association>(),
image = "antoinoxlephar",
description =
"A workshop dedicated to teaching strategies for successful social media marketing.",
catchyDescription = "Best concert everrrrr!",
price = 15.0,
date = Timestamp(Date(2024 - 1900, 7, 30)), // August 30, 2024
location = Location(5.0, 5.0, "USA"),
types = listOf(EventType.OTHER)),
Event(
uid = UUID.randomUUID().toString(),
title = "Jam Session: Local Artists",
organisers = MockReferenceList<Association>(),
taggedAssociations = MockReferenceList<Association>(),
image = "photo_2024_10_08_14_57_48",
description =
"An evening of music with local artists. Bring your instruments or just enjoy the show!",
catchyDescription = "Support local talent in this open jam session!",
price = 0.0,
date = Timestamp(Date(2024 - 1900, 3, 12)), // April 12, 2024
location = Location(6.0, 6.0, "USA"),
types = listOf(EventType.JAM)))
onSuccess(events)
} catch (e: Exception) {
onFailure(e)
}
}

// Mock implementation for getting events by association
override fun getEventsOfAssociation(
association: String,
onSuccess: (List<Event>) -> Unit,
onFailure: (Exception) -> Unit
) {
// Filter mock events by tagged associations
getEvents(
{ events ->
onSuccess(events.filter { it.taggedAssociations.list.value.isEmpty() })
}, // Now filtering for empty tagged associations
onFailure)
}

// Mock implementation for getting events between two dates
override fun getNextEventsFromDateToDate(
startDate: Timestamp,
endDate: Timestamp,
onSuccess: (List<Event>) -> Unit,
onFailure: (Exception) -> Unit
) {
// Filter mock events by date range
getEvents(
{ events -> onSuccess(events.filter { it.date >= startDate && it.date <= endDate }) },
onFailure)
}

// Mock implementation to generate a new UID
override fun getNewUid(): String {
return UUID.randomUUID().toString()
}

// Mock implementation to add an event
override fun addEvent(event: Event, onSuccess: () -> Unit, onFailure: (Exception) -> Unit) {
// This is a mock, so we assume the event is added successfully
onSuccess()
}

// Mock implementation to delete an event by ID
override fun deleteEventById(id: String, onSuccess: () -> Unit, onFailure: (Exception) -> Unit) {
// This is a mock, so we assume the event is deleted successfully
onSuccess()
}
}
Loading