git.iko.soy/opencode-android
on f2ff2eb help sign in
opencode-android/app/src/test/java/soy/iko/opencode/ui/server/ServerEditViewModelTest.kt
230 lines 8.0 kB history raw
1@file:OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
3package soy.iko.opencode.ui.server
5import kotlinx.coroutines.Dispatchers
6import kotlinx.coroutines.ExperimentalCoroutinesApi
7import kotlinx.coroutines.test.StandardTestDispatcher
8import kotlinx.coroutines.test.TestScope
9import kotlinx.coroutines.test.resetMain
10import kotlinx.coroutines.test.runTest
11import kotlinx.coroutines.test.setMain
12import org.junit.After
13import org.junit.Assert.assertEquals
14import org.junit.Assert.assertFalse
15import org.junit.Assert.assertNotNull
16import org.junit.Assert.assertNull
17import org.junit.Assert.assertTrue
18import org.junit.Before
19import org.junit.Test
20import soy.iko.opencode.data.model.ServerProfile
21import soy.iko.opencode.ui.testing.FakeAppContainer
23class ServerEditViewModelTest {
24 private val testDispatcher = StandardTestDispatcher()
25 private val testScope = TestScope(testDispatcher)
27 @Before
28 fun setUp() {
29 Dispatchers.setMain(testDispatcher)
30 }
32 @After
33 fun tearDown() {
34 Dispatchers.resetMain()
35 }
37 private fun makeVm(container: FakeAppContainer, profileId: String? = null): ServerEditViewModel {
38 val vm = ServerEditViewModel(container, profileId)
39 testScope.testScheduler.advanceUntilIdle()
40 return vm
41 }
43 // --- new profile ---
45 @Test
46 fun init_newProfile_loadsEmptyForm() = testScope.runTest {
47 val container = FakeAppContainer()
48 val vm = makeVm(container, profileId = null)
49 assertTrue(vm.state.value.loaded)
50 assertTrue(vm.state.value.isNew)
51 assertEquals("", vm.state.value.baseUrl)
52 assertEquals("", vm.state.value.label)
53 assertFalse(vm.state.value.saving)
54 assertNull(vm.state.value.error)
55 }
57 // --- existing profile ---
59 @Test
60 fun init_existingProfile_loadsFormFields() = testScope.runTest {
61 val container = FakeAppContainer()
62 val profile = ServerProfile(
63 id = "p1",
64 label = "My Server",
65 baseUrl = "http://localhost:3000",
66 username = "admin",
67 password = "secret",
68 )
69 container.fakeProfileStore.setProfiles(listOf(profile))
70 val vm = makeVm(container, profileId = "p1")
71 assertTrue(vm.state.value.loaded)
72 assertFalse(vm.state.value.isNew)
73 assertEquals("p1", vm.state.value.id)
74 assertEquals("My Server", vm.state.value.label)
75 assertEquals("http://localhost:3000", vm.state.value.baseUrl)
76 assertEquals("admin", vm.state.value.username)
77 assertEquals("secret", vm.state.value.password)
78 }
80 @Test
81 fun init_nonExistentProfile_showsError() = testScope.runTest {
82 val container = FakeAppContainer()
83 container.fakeProfileStore.setProfiles(emptyList())
84 val vm = makeVm(container, profileId = "nonexistent")
85 assertTrue(vm.state.value.loaded)
86 assertNotNull(vm.state.value.error)
87 }
89 // --- update() ---
91 @Test
92 fun update_modifiesState() = testScope.runTest {
93 val container = FakeAppContainer()
94 val vm = makeVm(container)
95 vm.update { it.copy(label = "New Label") }
96 assertEquals("New Label", vm.state.value.label)
97 }
99 // --- canSave validation ---
101 @Test
102 fun canSave_falseForBlankBaseUrl() = testScope.runTest {
103 val container = FakeAppContainer()
104 val vm = makeVm(container)
105 vm.update { it.copy(baseUrl = "") }
106 assertFalse(vm.state.value.canSave)
107 }
109 @Test
110 fun canSave_falseForInvalidUrl() = testScope.runTest {
111 val container = FakeAppContainer()
112 val vm = makeVm(container)
113 vm.update { it.copy(baseUrl = "not a url") }
114 assertFalse(vm.state.value.canSave)
115 }
117 @Test
118 fun canSave_trueForValidHttpUrl() = testScope.runTest {
119 val container = FakeAppContainer()
120 val vm = makeVm(container)
121 vm.update { it.copy(baseUrl = "http://localhost:3000") }
122 assertTrue(vm.state.value.canSave)
123 }
125 @Test
126 fun canSave_trueForValidHttpsUrl() = testScope.runTest {
127 val container = FakeAppContainer()
128 val vm = makeVm(container)
129 vm.update { it.copy(baseUrl = "https://example.com") }
130 assertTrue(vm.state.value.canSave)
131 }
133 @Test
134 fun canSave_falseForFtpUrl() = testScope.runTest {
135 val container = FakeAppContainer()
136 val vm = makeVm(container)
137 vm.update { it.copy(baseUrl = "ftp://example.com") }
138 assertFalse(vm.state.value.canSave)
139 }
141 // --- save() ---
143 @Test
144 fun save_persistsProfileAndCallsOnDone() = testScope.runTest {
145 val container = FakeAppContainer()
146 val vm = makeVm(container)
147 vm.update { it.copy(baseUrl = "http://localhost:3000", label = "Test") }
148 var doneCalled = false
149 vm.save { doneCalled = true }
150 testScheduler.advanceUntilIdle()
151 assertTrue(doneCalled)
152 assertNotNull(container.fakeProfileStore.savedProfile)
153 assertEquals("http://localhost:3000", container.fakeProfileStore.savedProfile?.baseUrl)
154 assertEquals("Test", container.fakeProfileStore.savedProfile?.label)
155 }
157 @Test
158 fun save_invalidUrlDoesNotSave() = testScope.runTest {
159 val container = FakeAppContainer()
160 val vm = makeVm(container)
161 vm.update { it.copy(baseUrl = "invalid") }
162 var doneCalled = false
163 vm.save { doneCalled = true }
164 testScheduler.advanceUntilIdle()
165 assertFalse(doneCalled)
166 assertNull(container.fakeProfileStore.savedProfile)
167 }
169 @Test
170 fun save_whileSavingDoesNotSaveAgain() = testScope.runTest {
171 val container = FakeAppContainer()
172 val vm = makeVm(container)
173 vm.update { it.copy(baseUrl = "http://localhost:3000") }
174 var doneCount = 0
175 vm.save { doneCount++ }
176 // While saving, try to save again
177 vm.save { doneCount++ }
178 testScheduler.advanceUntilIdle()
179 assertEquals(1, doneCount)
180 }
182 @Test
183 fun save_generatesIdForNewProfile() = testScope.runTest {
184 val container = FakeAppContainer()
185 val vm = makeVm(container)
186 vm.update { it.copy(baseUrl = "http://localhost:3000") }
187 vm.save { }
188 testScheduler.advanceUntilIdle()
189 val saved = container.fakeProfileStore.savedProfile
190 assertNotNull(saved?.id)
191 assertTrue(saved!!.id.isNotEmpty())
192 }
194 @Test
195 fun save_preservesIdForExistingProfile() = testScope.runTest {
196 val container = FakeAppContainer()
197 val profile = ServerProfile(id = "existing-id", label = "Old", baseUrl = "http://old")
198 container.fakeProfileStore.setProfiles(listOf(profile))
199 val vm = makeVm(container, profileId = "existing-id")
200 vm.update { it.copy(baseUrl = "http://new") }
201 vm.save { }
202 testScheduler.advanceUntilIdle()
203 assertEquals("existing-id", container.fakeProfileStore.savedProfile?.id)
204 }
206 @Test
207 fun save_trimsFields() = testScope.runTest {
208 val container = FakeAppContainer()
209 val vm = makeVm(container)
210 vm.update { it.copy(baseUrl = " http://localhost:3000 ", label = " Test ", username = " admin ") }
211 vm.save { }
212 testScheduler.advanceUntilIdle()
213 val saved = container.fakeProfileStore.savedProfile!!
214 assertEquals("http://localhost:3000", saved.baseUrl)
215 assertEquals("Test", saved.label)
216 assertEquals("admin", saved.username)
217 }
219 @Test
220 fun save_emptyUsernameClearsCredentials() = testScope.runTest {
221 val container = FakeAppContainer()
222 val vm = makeVm(container)
223 vm.update { it.copy(baseUrl = "http://localhost:3000", username = "", password = "") }
224 vm.save { }
225 testScheduler.advanceUntilIdle()
226 val saved = container.fakeProfileStore.savedProfile!!
227 assertNull(saved.username)
228 assertNull(saved.password)
229 }