Files
myice-kmp/composeApp/src/commonTest/kotlin/ch/parano/myice/viewmodel/ScheduleViewModelTest.kt
T
herel ed97b2a1f0 license: add GPL-3.0-or-later license
- Add full LICENSE file (GPL-3.0)
- Add SPDX headers to all 44 Kotlin source files
- Add SPDX headers to 3 iOS Swift/Info.plist files
- Update README.md and AGENTS.md with license info
2026-07-09 16:19:38 +02:00

230 lines
8.7 KiB
Kotlin

// SPDX-License-Identifier: GPL-3.0-or-later
// MyIce Kotlin Multiplatform — schedule/convocation viewer
// Copyright (C) 2026 parano.ch
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package ch.parano.myice.viewmodel
import ch.parano.myice.cache.ScheduleCache
import ch.parano.myice.filter.TypeFilter
import ch.parano.myice.models.Account
import ch.parano.myice.models.Event
import ch.parano.myice.network.ApiService
import com.russhwolf.settings.MapSettings
import io.ktor.client.HttpClient
import io.ktor.client.engine.mock.MockEngine
import io.ktor.client.engine.mock.MockEngineConfig
import io.ktor.client.engine.mock.respond
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.http.headersOf
import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import kotlinx.serialization.json.Json
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertTrue
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class ScheduleViewModelTest {
@BeforeTest
fun setUpDispatcher() {
Dispatchers.setMain(UnconfinedTestDispatcher())
}
@AfterTest
fun tearDownDispatcher() {
Dispatchers.resetMain()
}
private val json = Json { ignoreUnknownKeys = true }
private fun mockApi(accountsJson: String, scheduleJson: String): ApiService {
val config = MockEngineConfig().apply {
addHandler { request ->
val path = request.url.encodedPath
val response = when {
path == "/accounts" -> accountsJson
path == "/schedule" -> scheduleJson
else -> "[]"
}
respond(
content = response,
status = HttpStatusCode.OK,
headers = headersOf(HttpHeaders.ContentType, "application/json"),
)
}
dispatcher = Dispatchers.Unconfined
}
val client = HttpClient(MockEngine(config)) {
install(ContentNegotiation) { json(json) }
}
return ApiService(client)
}
private fun sampleEvent(
id: String = "1",
agegroup: String = "U13 (Elite)",
name: String = "U13 Elite - HC Ajoie",
eventType: String = "Jeu",
) = Event(
idEvent = id, agegroup = agegroup, name = name, title = "Title",
opponent = "Opp", place = "Place", start = "2024-11-10T14:00:00",
end = "2024-11-10T16:15:00", color = "#e4222e", eventType = eventType,
)
@Test
fun testLoadAccounts() = runTest {
val api = mockApi(
"""[{"name":"default","label":"Default"}]""",
"[]",
)
val cache = ScheduleCache(MapSettings())
val vm = ScheduleViewModel(api, cache)
vm.loadAccounts()
assertEquals(1, vm.state.value.accounts.size)
assertEquals("default", vm.state.value.selectedAccount)
}
@Test
fun testRefreshScheduleStoresEvents() = runTest {
val scheduleJson = """
[{"id_event":"1","agegroup":"U13 (Elite)","name":"U13 Elite - HC Ajoie","title":"T","opponent":"O","place":"P","start":"2024-11-10T14:00:00","end":"2024-11-10T16:15:00","color":"#e4222e","event":"Jeu"}]
""".trimIndent()
val api = mockApi("""[{"name":"default","label":"Default"}]""", scheduleJson)
val cache = ScheduleCache(MapSettings())
val vm = ScheduleViewModel(api, cache)
vm.loadAccounts()
vm.refreshSchedule()
assertEquals(1, vm.state.value.events.size)
assertEquals("1", vm.state.value.events[0].idEvent)
}
@Test
fun testFilteredEventsDefaultGamesOnly() = runTest {
val scheduleJson = """
[{"id_event":"1","agegroup":"U13","name":"U13 - A","title":"T","opponent":"O","place":"P","start":"s","end":"e","event":"Jeu"},
{"id_event":"2","agegroup":"U13","name":"U13 - B","title":"T","opponent":"","place":"P","start":"s","end":"e"}]
""".trimIndent()
val api = mockApi("""[{"name":"default","label":"Default"}]""", scheduleJson)
val cache = ScheduleCache(MapSettings())
val vm = ScheduleViewModel(api, cache)
vm.loadAccounts()
vm.refreshSchedule()
assertEquals(2, vm.state.value.events.size)
assertEquals(1, vm.state.value.filteredEvents.size)
assertEquals("1", vm.state.value.filteredEvents[0].idEvent)
}
@Test
fun testSetTypeFilterAll() = runTest {
val scheduleJson = """
[{"id_event":"1","agegroup":"U13","name":"U13 - A","title":"T","opponent":"O","place":"P","start":"s","end":"e","event":"Jeu"},
{"id_event":"2","agegroup":"U13","name":"U13 - B","title":"T","opponent":"","place":"P","start":"s","end":"e"}]
""".trimIndent()
val api = mockApi("""[{"name":"default","label":"Default"}]""", scheduleJson)
val cache = ScheduleCache(MapSettings())
val vm = ScheduleViewModel(api, cache)
vm.loadAccounts()
vm.refreshSchedule()
vm.setTypeFilter(TypeFilter.ALL)
assertEquals(2, vm.state.value.filteredEvents.size)
}
@Test
fun testSetAgegroup() = runTest {
val scheduleJson = """
[{"id_event":"1","agegroup":"U13","name":"U13 - A","title":"T","opponent":"O","place":"P","start":"s","end":"e","event":"Jeu"},
{"id_event":"2","agegroup":"U15","name":"U15 - B","title":"T","opponent":"O","place":"P","start":"s","end":"e","event":"Jeu"}]
""".trimIndent()
val api = mockApi("""[{"name":"default","label":"Default"}]""", scheduleJson)
val cache = ScheduleCache(MapSettings())
val vm = ScheduleViewModel(api, cache)
vm.loadAccounts()
vm.refreshSchedule()
vm.setAgegroup("U15")
assertEquals(1, vm.state.value.filteredEvents.size)
assertEquals("2", vm.state.value.filteredEvents[0].idEvent)
}
@Test
fun testSetSubgroup() = runTest {
val scheduleJson = """
[{"id_event":"1","agegroup":"U13","name":"U13 Elite - HC Ajoie","title":"T","opponent":"O","place":"P","start":"s","end":"e","event":"Jeu"},
{"id_event":"2","agegroup":"U13","name":"U13 Top - HC Geneva","title":"T","opponent":"O","place":"P","start":"s","end":"e","event":"Jeu"}]
""".trimIndent()
val api = mockApi("""[{"name":"default","label":"Default"}]""", scheduleJson)
val cache = ScheduleCache(MapSettings())
val vm = ScheduleViewModel(api, cache)
vm.loadAccounts()
vm.refreshSchedule()
vm.setSubgroup("U13 Elite")
assertEquals(1, vm.state.value.filteredEvents.size)
assertEquals("1", vm.state.value.filteredEvents[0].idEvent)
}
@Test
fun testCacheLoadOnLoadCachedSchedule() = runTest {
val api = mockApi("""[{"name":"default","label":"Default"}]""", "[]")
val cache = ScheduleCache(MapSettings())
cache.saveEvents("default", listOf(sampleEvent("1")))
val vm = ScheduleViewModel(api, cache)
vm.loadAccounts()
vm.loadCachedSchedule()
assertEquals(1, vm.state.value.events.size)
assertFalse(vm.state.value.isLoading)
}
@Test
fun testAgegroupsComputed() = runTest {
val scheduleJson = """
[{"id_event":"1","agegroup":"U15","name":"N","title":"T","opponent":"","place":"P","start":"s","end":"e","event":"Jeu"},
{"id_event":"2","agegroup":"U13","name":"N","title":"T","opponent":"","place":"P","start":"s","end":"e","event":"Jeu"}]
""".trimIndent()
val api = mockApi("""[{"name":"default","label":"Default"}]""", scheduleJson)
val cache = ScheduleCache(MapSettings())
val vm = ScheduleViewModel(api, cache)
vm.loadAccounts()
vm.refreshSchedule()
assertEquals(listOf("U13", "U15"), vm.state.value.agegroups)
}
}