Browse Source

First pass at user state processing

tags/v0.1.0
Chris Smith 5 years ago
parent
commit
3be57d029b

+ 2
- 6
src/main/kotlin/com/dmdirc/ktirc/events/ChannelStateHandler.kt View File

@@ -3,7 +3,6 @@ package com.dmdirc.ktirc.events
3 3
 import com.dmdirc.ktirc.IrcClient
4 4
 import com.dmdirc.ktirc.model.ChannelState
5 5
 import com.dmdirc.ktirc.model.ChannelUser
6
-import com.dmdirc.ktirc.model.ServerFeature
7 6
 import com.dmdirc.ktirc.util.logger
8 7
 
9 8
 class ChannelStateHandler : EventHandler {
@@ -49,11 +48,8 @@ class ChannelStateHandler : EventHandler {
49 48
             channel.receivingUserList = true
50 49
         }
51 50
 
52
-        val modePrefixes = client.serverState.features[ServerFeature.ModePrefixes]!!
53
-        for (user in event.names) {
54
-            user.takeWhile { modePrefixes.isPrefix(it) }.let { prefix ->
55
-                channel.users += ChannelUser(user.nickname(prefix.length), modePrefixes.getModes(prefix))
56
-            }
51
+        event.toModesAndUsers(client).forEach { (modes, user) ->
52
+            channel.users += ChannelUser(user.nickname, modes)
57 53
         }
58 54
     }
59 55
 

+ 3
- 2
src/main/kotlin/com/dmdirc/ktirc/events/EventHandler.kt View File

@@ -8,9 +8,10 @@ interface EventHandler {
8 8
 
9 9
 }
10 10
 
11
-val eventHandlers = setOf(
11
+val eventHandlers = listOf(
12 12
         CapabilitiesHandler(),
13 13
         ChannelStateHandler(),
14 14
         PingHandler(),
15
-        ServerStateHandler()
15
+        ServerStateHandler(),
16
+        UserStateHandler()
16 17
 )

+ 14
- 0
src/main/kotlin/com/dmdirc/ktirc/events/EventUtils.kt View File

@@ -0,0 +1,14 @@
1
+package com.dmdirc.ktirc.events
2
+
3
+import com.dmdirc.ktirc.IrcClient
4
+import com.dmdirc.ktirc.model.ServerFeature
5
+import com.dmdirc.ktirc.model.asUser
6
+
7
+fun ChannelNamesReceived.toModesAndUsers(client: IrcClient) = sequence {
8
+    val modePrefixes = client.serverState.features[ServerFeature.ModePrefixes]!!
9
+    for (user in names) {
10
+        user.takeWhile { modePrefixes.isPrefix(it) }.let { prefix ->
11
+            yield(Pair(modePrefixes.getModes(prefix), user.substring(prefix.length).asUser()))
12
+        }
13
+    }
14
+}.toList()

+ 48
- 0
src/main/kotlin/com/dmdirc/ktirc/events/UserStateHandler.kt View File

@@ -0,0 +1,48 @@
1
+package com.dmdirc.ktirc.events
2
+
3
+import com.dmdirc.ktirc.IrcClient
4
+import com.dmdirc.ktirc.model.UserState
5
+
6
+class UserStateHandler : EventHandler {
7
+
8
+    override suspend fun processEvent(client: IrcClient, event: IrcEvent) {
9
+        when (event) {
10
+            is ChannelJoined -> handleJoin(client.userState, event)
11
+            is ChannelParted -> handlePart(client, event)
12
+            is ChannelNamesReceived  -> handleNamesReceived(client, event)
13
+            is UserQuit -> handleQuit(client.userState, event)
14
+        }
15
+    }
16
+
17
+    private fun handleJoin(state: UserState, event: ChannelJoined) {
18
+        state.addToChannel(event.user, event.channel)
19
+        state.update(event.user)
20
+    }
21
+
22
+    private fun handlePart(client: IrcClient, event: ChannelParted) {
23
+        if (client.isLocalUser(event.user)) {
24
+            // Remove channel from all users
25
+            client.userState.forEach { it.channels -= event.channel }
26
+            client.userState.removeIf { it.channels.isEmpty() && !client.isLocalUser(it.details) }
27
+        } else {
28
+            client.userState[event.user]?.channels?.let {
29
+                it -= event.channel
30
+                if (it.isEmpty()) {
31
+                    client.userState -= event.user
32
+                }
33
+            }
34
+        }
35
+    }
36
+
37
+    private fun handleNamesReceived(client: IrcClient, event: ChannelNamesReceived) {
38
+        event.toModesAndUsers(client).forEach { (_, user) ->
39
+            client.userState.addToChannel(user, event.channel)
40
+            client.userState.update(user)
41
+        }
42
+    }
43
+
44
+    private fun handleQuit(state: UserState, event: UserQuit) {
45
+        state -= event.user
46
+    }
47
+
48
+}

+ 31
- 0
src/main/kotlin/com/dmdirc/ktirc/model/CaseInsensitiveSet.kt View File

@@ -0,0 +1,31 @@
1
+package com.dmdirc.ktirc.model
2
+
3
+import com.dmdirc.ktirc.io.CaseMapping
4
+
5
+/**
6
+ * Maintains a set of strings that are compared case-insensitively according to the provided [CaseMapping]
7
+ *
8
+ * If the case mapping changes during the lifetime of this set, it is presumed that all items will also be
9
+ * unique in the new case mapping. Otherwise, the behaviour of this class is undefined.
10
+ */
11
+class CaseInsensitiveSet(private val caseMappingProvider: () -> CaseMapping) : Iterable<String> {
12
+
13
+    private val items = HashSet<String>()
14
+
15
+    operator fun plusAssign(item: String) {
16
+        if (!contains(item)) {
17
+            items += item
18
+        }
19
+    }
20
+
21
+    operator fun minusAssign(item: String) {
22
+        items.removeIf { caseMappingProvider().areEquivalent(it, item) }
23
+    }
24
+
25
+    operator fun contains(item: String) = items.any { caseMappingProvider().areEquivalent(it, item) }
26
+
27
+    override operator fun iterator() = items.iterator()
28
+
29
+    fun isEmpty() = items.isEmpty()
30
+
31
+}

+ 2
- 0
src/main/kotlin/com/dmdirc/ktirc/model/Maps.kt View File

@@ -23,6 +23,8 @@ abstract class CaseInsensitiveMap<T>(private val caseMappingProvider: () -> Case
23 23
 
24 24
     fun clear() = values.clear()
25 25
 
26
+    fun removeIf(predicate: (T) -> Boolean) = values.removeIf(predicate)
27
+
26 28
 }
27 29
 
28 30
 class ChannelStateMap(caseMappingProvider: () -> CaseMapping) : CaseInsensitiveMap<ChannelState>(caseMappingProvider, ChannelState::name)

+ 8
- 7
src/main/kotlin/com/dmdirc/ktirc/model/User.kt View File

@@ -18,17 +18,18 @@ data class User(
18 18
     }
19 19
 }
20 20
 
21
-fun ByteArray.asUser(): User {
22
-    val string = String(this)
23
-    val identOffset = string.indexOf('!')
21
+fun ByteArray.asUser() = String(this).asUser()
22
+
23
+fun String.asUser(): User {
24
+    val identOffset = indexOf('!')
24 25
     return if (identOffset >= 0) {
25
-        val hostOffset = string.indexOf('@', identOffset)
26
+        val hostOffset = indexOf('@', identOffset)
26 27
         if (hostOffset >= 0) {
27
-            User(string.substring(0 until identOffset), string.substring(identOffset + 1 until hostOffset), string.substring(hostOffset + 1))
28
+            User(substring(0 until identOffset), substring(identOffset + 1 until hostOffset), substring(hostOffset + 1))
28 29
         } else {
29
-            User(string.substring(0 until identOffset), string.substring(identOffset + 1))
30
+            User(substring(0 until identOffset), substring(identOffset + 1))
30 31
         }
31 32
     } else {
32
-        User(string)
33
+        User(this)
33 34
     }
34 35
 }

+ 20
- 6
src/main/kotlin/com/dmdirc/ktirc/model/UserState.kt View File

@@ -2,23 +2,37 @@ package com.dmdirc.ktirc.model
2 2
 
3 3
 import com.dmdirc.ktirc.io.CaseMapping
4 4
 
5
-class UserState(caseMappingProvider: () -> CaseMapping) {
5
+class UserState(private val caseMappingProvider: () -> CaseMapping): Iterable<KnownUser> {
6 6
 
7 7
     private val users = UserMap(caseMappingProvider)
8 8
 
9 9
     operator fun get(nickname: String) = users[nickname]
10
-    operator fun plusAssign(details: User) { users += KnownUser(details) }
10
+    operator fun get(user: User) = users[user.nickname]
11
+
12
+    operator fun plusAssign(details: User) { users += KnownUser(caseMappingProvider, details) }
11 13
     operator fun minusAssign(details: User) { users -= details.nickname }
12 14
 
13
-    fun update(details: User, oldNick: String = details.nickname) {
14
-        users[oldNick]?.details?.updateFrom(details)
15
+    override operator fun iterator() = users.iterator()
16
+
17
+    fun removeIf(predicate: (KnownUser) -> Boolean) = users.removeIf(predicate)
18
+
19
+    fun update(user: User, oldNick: String = user.nickname) {
20
+        users[oldNick]?.details?.updateFrom(user)
21
+    }
22
+
23
+    fun addToChannel(user: User, channel: String) {
24
+        users[user.nickname]?.let {
25
+            it += channel
26
+        } ?: run {
27
+            users += KnownUser(caseMappingProvider, user).apply { channels += channel }
28
+        }
15 29
     }
16 30
 
17 31
 }
18 32
 
19
-class KnownUser(val details: User) {
33
+class KnownUser(caseMappingProvider: () -> CaseMapping, val details: User) {
20 34
 
21
-    val channels = mutableListOf<String>()
35
+    val channels = CaseInsensitiveSet(caseMappingProvider)
22 36
 
23 37
     operator fun plusAssign(channel: String) { channels += channel }
24 38
     operator fun minusAssign(channel: String) { channels -= channel }

+ 66
- 0
src/test/kotlin/com/dmdirc/ktirc/events/EventUtilsTest.kt View File

@@ -0,0 +1,66 @@
1
+package com.dmdirc.ktirc.events
2
+
3
+import com.dmdirc.ktirc.IrcClient
4
+import com.dmdirc.ktirc.TestConstants
5
+import com.dmdirc.ktirc.model.ModePrefixMapping
6
+import com.dmdirc.ktirc.model.ServerFeature
7
+import com.dmdirc.ktirc.model.ServerState
8
+import com.nhaarman.mockitokotlin2.doReturn
9
+import com.nhaarman.mockitokotlin2.mock
10
+import org.junit.jupiter.api.Assertions.assertEquals
11
+import org.junit.jupiter.api.BeforeEach
12
+import org.junit.jupiter.api.Test
13
+
14
+internal class EventUtilsTest {
15
+
16
+    private val serverState = ServerState("")
17
+    private val ircClient = mock<IrcClient> {
18
+        on { serverState } doReturn serverState
19
+    }
20
+
21
+    @BeforeEach
22
+    fun setUp() {
23
+        serverState.features[ServerFeature.ModePrefixes] = ModePrefixMapping("ov", "@+")
24
+    }
25
+
26
+    @Test
27
+    fun `ChannelNamesReceived#toModesAndUsers parses nicknames and mode prefixes`() {
28
+        val event = ChannelNamesReceived(TestConstants.time, "#thegibson", listOf(
29
+                "@+acidBurn", "@zeroCool", "thePlague"
30
+        ))
31
+
32
+        val result = event.toModesAndUsers(ircClient)
33
+        assertEquals(3, result.size)
34
+
35
+        assertEquals("ov", result[0].first)
36
+        assertEquals("acidBurn", result[0].second.nickname)
37
+
38
+        assertEquals("o", result[1].first)
39
+        assertEquals("zeroCool", result[1].second.nickname)
40
+
41
+        assertEquals("", result[2].first)
42
+        assertEquals("thePlague", result[2].second.nickname)
43
+    }
44
+
45
+    @Test
46
+    fun `ChannelNamesReceived#toModesAndUsers parses extended joins with prefixes`() {
47
+        val event = ChannelNamesReceived(TestConstants.time, "#thegibson", listOf(
48
+                "@+acidBurn!libby@root.localhost", "zeroCool!dade@root.localhost"
49
+        ))
50
+
51
+        val result = event.toModesAndUsers(ircClient)
52
+        assertEquals(2, result.size)
53
+
54
+        assertEquals("ov", result[0].first)
55
+        assertEquals("acidBurn", result[0].second.nickname)
56
+        assertEquals("libby", result[0].second.ident)
57
+        assertEquals("root.localhost", result[0].second.hostname)
58
+
59
+        assertEquals("", result[1].first)
60
+        assertEquals("zeroCool", result[1].second.nickname)
61
+        assertEquals("dade", result[1].second.ident)
62
+        assertEquals("root.localhost", result[1].second.hostname)
63
+    }
64
+
65
+
66
+}

+ 161
- 0
src/test/kotlin/com/dmdirc/ktirc/events/UserStateHandlerTest.kt View File

@@ -0,0 +1,161 @@
1
+package com.dmdirc.ktirc.events
2
+
3
+import com.dmdirc.ktirc.IrcClient
4
+import com.dmdirc.ktirc.TestConstants
5
+import com.dmdirc.ktirc.io.CaseMapping
6
+import com.dmdirc.ktirc.model.*
7
+import com.nhaarman.mockitokotlin2.argForWhich
8
+import com.nhaarman.mockitokotlin2.doReturn
9
+import com.nhaarman.mockitokotlin2.mock
10
+import kotlinx.coroutines.runBlocking
11
+import org.junit.jupiter.api.Assertions.*
12
+import org.junit.jupiter.api.BeforeEach
13
+import org.junit.jupiter.api.Test
14
+
15
+internal class UserStateHandlerTest {
16
+
17
+    private val serverState = ServerState("")
18
+    private val userState = UserState { CaseMapping.Rfc }
19
+
20
+    private val ircClient = mock<IrcClient> {
21
+        on { serverState } doReturn serverState
22
+        on { userState } doReturn userState
23
+        on { isLocalUser(argForWhich { nickname == "zeroCool" }) } doReturn true
24
+    }
25
+
26
+    private val handler = UserStateHandler()
27
+
28
+    @BeforeEach
29
+    fun setUp() {
30
+        serverState.features[ServerFeature.ModePrefixes] = ModePrefixMapping("ov", "@+")
31
+    }
32
+
33
+    @Test
34
+    fun `adds channel to user on join`() {
35
+        runBlocking {
36
+            userState += User("acidBurn")
37
+
38
+            handler.processEvent(ircClient, ChannelJoined(TestConstants.time, User("acidBurn", "libby", "root.localhost"), "#thegibson"))
39
+
40
+            assertEquals(listOf("#thegibson"), userState["acidBurn"]?.channels?.toList())
41
+        }
42
+    }
43
+
44
+    @Test
45
+    fun `updates user info on join`() {
46
+        runBlocking {
47
+            userState += User("acidBurn")
48
+
49
+            handler.processEvent(ircClient, ChannelJoined(TestConstants.time, User("acidBurn", "libby", "root.localhost"), "#thegibson"))
50
+
51
+            val details = userState["acidBurn"]?.details!!
52
+            assertEquals("libby", details.ident)
53
+            assertEquals("root.localhost", details.hostname)
54
+        }
55
+    }
56
+
57
+    @Test
58
+    fun `removes channel from user on part`() {
59
+        runBlocking {
60
+            userState += User("acidBurn")
61
+            userState.addToChannel(User("acidBurn"), "#thegibson")
62
+            userState.addToChannel(User("acidBurn"), "#dumpsterdiving")
63
+
64
+            handler.processEvent(ircClient, ChannelParted(TestConstants.time, User("acidBurn", "libby", "root.localhost"), "#dumpsterdiving"))
65
+
66
+            assertEquals(listOf("#thegibson"), userState["acidBurn"]?.channels?.toList())
67
+        }
68
+    }
69
+
70
+    @Test
71
+    fun `removes user on part from last channel`() {
72
+        runBlocking {
73
+            userState += User("acidBurn")
74
+            userState.addToChannel(User("acidBurn"), "#dumpsterdiving")
75
+
76
+            handler.processEvent(ircClient, ChannelParted(TestConstants.time, User("acidBurn", "libby", "root.localhost"), "#dumpsterdiving"))
77
+
78
+            assertNull(userState["acidBurn"])
79
+        }
80
+    }
81
+
82
+    @Test
83
+    fun `removes channel from all users on local part`() {
84
+        runBlocking {
85
+            userState += User("acidBurn")
86
+            userState.addToChannel(User("acidBurn"), "#dumpsterdiving")
87
+            userState.addToChannel(User("acidBurn"), "#thegibson")
88
+
89
+            userState += User("zeroCool")
90
+            userState.addToChannel(User("zeroCool"), "#dumpsterdiving")
91
+            userState.addToChannel(User("zeroCool"), "#thegibson")
92
+
93
+            handler.processEvent(ircClient, ChannelParted(TestConstants.time, User("zeroCool", "dade", "root.localhost"), "#dumpsterdiving"))
94
+
95
+            assertEquals(listOf("#thegibson"), userState["acidBurn"]?.channels?.toList())
96
+            assertEquals(listOf("#thegibson"), userState["zeroCool"]?.channels?.toList())
97
+        }
98
+    }
99
+
100
+    @Test
101
+    fun `removes remote users with no remaining channels on local part`() {
102
+        runBlocking {
103
+            userState += User("acidBurn")
104
+            userState.addToChannel(User("acidBurn"), "#dumpsterdiving")
105
+
106
+            handler.processEvent(ircClient, ChannelParted(TestConstants.time, User("zeroCool", "dade", "root.localhost"), "#dumpsterdiving"))
107
+
108
+            assertNull(userState["acidBurn"])
109
+        }
110
+    }
111
+
112
+    @Test
113
+    fun `keeps local user with no remaining channels after local part`() {
114
+        runBlocking {
115
+            userState += User("zeroCool")
116
+            userState.addToChannel(User("zeroCool"), "#dumpsterdiving")
117
+
118
+            handler.processEvent(ircClient, ChannelParted(TestConstants.time, User("zeroCool", "dade", "root.localhost"), "#dumpsterdiving"))
119
+
120
+            assertNotNull(userState["zeroCool"])
121
+        }
122
+    }
123
+
124
+    @Test
125
+    fun `removes user entirely on quit`() {
126
+        runBlocking {
127
+            userState += User("acidBurn")
128
+            userState.addToChannel(User("acidBurn"), "#dumpsterdiving")
129
+
130
+            handler.processEvent(ircClient, UserQuit(TestConstants.time, User("acidBurn", "libby", "root.localhost")))
131
+
132
+            assertNull(userState["acidBurn"])
133
+        }
134
+    }
135
+
136
+    @Test
137
+    fun `adds users to channels on names received`() {
138
+        runBlocking {
139
+            userState += User("acidBurn")
140
+            userState.addToChannel(User("acidBurn"), "#dumpsterdiving")
141
+
142
+            handler.processEvent(ircClient, ChannelNamesReceived(TestConstants.time, "#thegibson", listOf("@acidBurn")))
143
+
144
+            assertEquals(listOf("#dumpsterdiving", "#thegibson"), userState["acidBurn"]?.channels?.toList())
145
+        }
146
+    }
147
+
148
+    @Test
149
+    fun `updates user details on names received`() {
150
+        runBlocking {
151
+            userState += User("acidBurn")
152
+            userState.addToChannel(User("acidBurn"), "#dumpsterdiving")
153
+
154
+            handler.processEvent(ircClient, ChannelNamesReceived(TestConstants.time, "#thegibson", listOf("@acidBurn!libby@root.localhost")))
155
+
156
+            val details = userState["acidBurn"]?.details!!
157
+            assertEquals("libby", details.ident)
158
+            assertEquals("root.localhost", details.hostname)
159
+        }
160
+    }
161
+}

+ 64
- 0
src/test/kotlin/com/dmdirc/ktirc/model/CaseInsensitiveSetTest.kt View File

@@ -0,0 +1,64 @@
1
+package com.dmdirc.ktirc.model
2
+
3
+import com.dmdirc.ktirc.io.CaseMapping
4
+import org.junit.jupiter.api.Assertions.*
5
+import org.junit.jupiter.api.Test
6
+
7
+internal class CaseInsensitiveSetTest {
8
+
9
+    val set = CaseInsensitiveSet { CaseMapping.Rfc }
10
+
11
+    @Test
12
+    fun `isEmpty returns true when there are no items`() {
13
+        assertTrue(set.isEmpty())
14
+
15
+        set += "gibson"
16
+        assertFalse(set.isEmpty())
17
+
18
+        set -= "gibson"
19
+        assertTrue(set.isEmpty())
20
+    }
21
+
22
+    @Test
23
+    fun `items can be added to the set`() {
24
+        set += "libby"
25
+        assertEquals(setOf("libby"), set.toSet())
26
+
27
+        set += "dade"
28
+        assertEquals(setOf("libby", "dade"), set.toSet())
29
+    }
30
+
31
+    @Test
32
+    fun `items can be removed from the set`() {
33
+        set += "acidBurn"
34
+        set += "zeroCool"
35
+        set -= "acidBurn"
36
+        assertEquals(setOf("zeroCool"), set.toSet())
37
+    }
38
+
39
+    @Test
40
+    fun `items can be removed from the set using a different case`() {
41
+        set += "acidBurn"
42
+        set += "zeroCool"
43
+        set -= "ACIDburn"
44
+        assertEquals(setOf("zeroCool"), set.toSet())
45
+    }
46
+
47
+    @Test
48
+    fun `adding the same item in a different case has no effect`() {
49
+        set += "acidBurn[]"
50
+        set += "Acidburn[]"
51
+        set += "acidBurn{}"
52
+        assertEquals(1, set.count())
53
+    }
54
+
55
+    @Test
56
+    fun `contains indicates if a case-insensitive match is in the set`() {
57
+        set += "acidBurn[]"
58
+        assertTrue("acidBurn[]" in set)
59
+        assertTrue("AcidBurn[]" in set)
60
+        assertTrue("acidBurn{}" in set)
61
+        assertFalse("zeroCool" in set)
62
+    }
63
+
64
+}

+ 9
- 7
src/test/kotlin/com/dmdirc/ktirc/model/KnownUserTest.kt View File

@@ -1,5 +1,6 @@
1 1
 package com.dmdirc.ktirc.model
2 2
 
3
+import com.dmdirc.ktirc.io.CaseMapping
3 4
 import org.junit.jupiter.api.Assertions.*
4 5
 import org.junit.jupiter.api.Test
5 6
 
@@ -7,30 +8,31 @@ internal class KnownUserTest {
7 8
 
8 9
     @Test
9 10
     fun `KnownUser can add channels`() {
10
-        val user = KnownUser(User("acidBurn"))
11
+        val user = KnownUser({ CaseMapping.Rfc }, User("acidBurn"))
11 12
         user += "#thegibson"
12 13
         user += "#dumpsterdiving"
13 14
 
14
-        assertEquals(2, user.channels.size)
15
+        assertEquals(2, user.channels.count())
15 16
         assertTrue("#thegibson" in user.channels)
16 17
         assertTrue("#dumpsterdiving" in user.channels)
17 18
     }
18 19
 
19 20
     @Test
20 21
     fun `KnownUser can remove channels`() {
21
-        val user = KnownUser(User("acidBurn"))
22
-        user.channels.addAll(listOf("#thegibson", "#dumpsterdiving"))
22
+        val user = KnownUser({ CaseMapping.Rfc }, User("acidBurn"))
23
+        user.channels += "#thegibson"
24
+        user.channels += "#dumpsterdiving"
23 25
         user -= "#thegibson"
24 26
 
25
-        assertEquals(1, user.channels.size)
27
+        assertEquals(1, user.channels.count())
26 28
         assertFalse("#thegibson" in user.channels)
27 29
         assertTrue("#dumpsterdiving" in user.channels)
28 30
     }
29 31
 
30 32
     @Test
31 33
     fun `KnownUser indicates if a channel is known`() {
32
-        val user = KnownUser(User("acidBurn"))
33
-        user.channels.addAll(listOf("#thegibson"))
34
+        val user = KnownUser({ CaseMapping.Rfc }, User("acidBurn"))
35
+        user.channels += "#thegibson"
34 36
 
35 37
         assertTrue("#thegibson" in user)
36 38
         assertFalse("#dumpsterdiving" in user)

+ 13
- 1
src/test/kotlin/com/dmdirc/ktirc/model/MapsTest.kt View File

@@ -99,6 +99,18 @@ internal class CaseInsensitiveMapTest {
99 99
         assertEquals(0, map.count())
100 100
     }
101 101
 
102
+    @Test
103
+    fun `removeIf removes matching items`() {
104
+        map += Wrapper("acidBurn")
105
+        map += Wrapper("zeroCool")
106
+        map += Wrapper("thePlague")
107
+
108
+        map.removeIf { it.name.length == 8 }
109
+
110
+        assertEquals(1, map.count())
111
+        assertTrue("thePlague" in map)
112
+    }
113
+
102 114
 }
103 115
 
104 116
 internal class ChannelStateMapTest {
@@ -132,7 +144,7 @@ internal class UserMapTest {
132 144
     @Test
133 145
     fun `UserMap maps users on nickname`() {
134 146
         val userMap = UserMap { CaseMapping.Rfc }
135
-        userMap += KnownUser(User("acidBurn"))
147
+        userMap += KnownUser({ CaseMapping.Rfc }, User("acidBurn"))
136 148
         assertTrue("acidBurn" in userMap)
137 149
         assertTrue("acidburn" in userMap)
138 150
         assertFalse("zerocool" in userMap)

+ 35
- 0
src/test/kotlin/com/dmdirc/ktirc/model/UserStateTest.kt View File

@@ -53,4 +53,39 @@ internal class UserStateTest {
53 53
         assertNull(userState["acidBurn"])
54 54
     }
55 55
 
56
+    @Test
57
+    fun `addToChannel adds new user if not known`() {
58
+        userState.addToChannel(User("acidBurn", "libby", "root.localhost"), "#thegibson")
59
+
60
+        val user = userState["acidburn"]!!
61
+        assertEquals("acidBurn", user.details.nickname)
62
+        assertEquals("libby", user.details.ident)
63
+        assertEquals("root.localhost", user.details.hostname)
64
+
65
+        assertEquals(1, user.channels.count())
66
+        assertTrue("#thegibson" in user.channels)
67
+    }
68
+
69
+    @Test
70
+    fun `addToChannel appends channel to existing user`() {
71
+        userState += User("acidBurn", "libby", "root.localhost")
72
+        userState.addToChannel(User("acidBurn"), "#thegibson")
73
+
74
+        val user = userState["acidburn"]!!
75
+        assertEquals(1, user.channels.count())
76
+        assertTrue("#thegibson" in user.channels)
77
+    }
78
+
79
+    @Test
80
+    fun `removeIf deletes all matching users`() {
81
+        userState += User("acidBurn", "libby", "root.localhost")
82
+        userState += User("zeroCool", "dade", "root.localhost")
83
+        userState += User("acidBurn2", "libby", "root.localhost")
84
+
85
+        userState.removeIf { it.details.nickname.startsWith("acidBurn") }
86
+
87
+        assertEquals(1, userState.count())
88
+        assertNotNull(userState["zeroCool"])
89
+    }
90
+
56 91
 }

Loading…
Cancel
Save