- 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
230 lines
8.7 KiB
Kotlin
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)
|
|
}
|
|
}
|