github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/uiinboxloader_test.go (about) 1 package chat 2 3 import ( 4 "encoding/json" 5 "testing" 6 "time" 7 8 "github.com/keybase/client/go/kbtest" 9 "github.com/keybase/client/go/protocol/chat1" 10 "github.com/keybase/client/go/protocol/gregor1" 11 "github.com/keybase/client/go/protocol/keybase1" 12 "github.com/stretchr/testify/require" 13 ) 14 15 func TestUIInboxLoaderLayout(t *testing.T) { 16 useRemoteMock = false 17 defer func() { useRemoteMock = true }() 18 ctc := makeChatTestContext(t, "TestUIInboxLoaderLayout", 3) 19 defer ctc.cleanup() 20 timeout := 2 * time.Second 21 22 users := ctc.users() 23 chatUI := kbtest.NewChatUI() 24 ctx := ctc.as(t, users[0]).startCtx 25 tc := ctc.world.Tcs[users[0].Username] 26 uid := gregor1.UID(users[0].GetUID().ToBytes()) 27 tc.G.UIRouter = kbtest.NewMockUIRouter(chatUI) 28 tc.ChatG.UIInboxLoader = NewUIInboxLoader(tc.Context()) 29 tc.ChatG.UIInboxLoader.Start(ctx, uid) 30 defer func() { <-tc.ChatG.UIInboxLoader.Stop(ctx) }() 31 tc.ChatG.UIInboxLoader.(*UIInboxLoader).testingLayoutForceMode = true 32 tc.ChatG.UIInboxLoader.(*UIInboxLoader).batchDelay = time.Hour 33 recvLayout := func() chat1.UIInboxLayout { 34 select { 35 case layout := <-chatUI.InboxLayoutCb: 36 return layout 37 case <-time.After(timeout): 38 require.Fail(t, "no layout received") 39 } 40 return chat1.UIInboxLayout{} 41 } 42 consumeAllLayout := func() chat1.UIInboxLayout { 43 var layout chat1.UIInboxLayout 44 for { 45 select { 46 case layout = <-chatUI.InboxLayoutCb: 47 case <-time.After(timeout): 48 return layout 49 } 50 } 51 } 52 53 var layout chat1.UIInboxLayout 54 t.Logf("basic") 55 conv1 := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 56 chat1.ConversationMembersType_IMPTEAMNATIVE, users[1]) 57 for i := 0; i < 2; i++ { 58 layout = recvLayout() 59 require.Equal(t, 1, len(layout.SmallTeams)) 60 require.Equal(t, conv1.Id.ConvIDStr(), layout.SmallTeams[0].ConvID) 61 } 62 conv2 := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 63 chat1.ConversationMembersType_IMPTEAMNATIVE, users[2]) 64 for i := 0; i < 2; i++ { 65 layout = recvLayout() 66 require.Equal(t, 2, len(layout.SmallTeams)) 67 require.Equal(t, conv2.Id.ConvIDStr(), layout.SmallTeams[0].ConvID) 68 require.Equal(t, conv1.Id.ConvIDStr(), layout.SmallTeams[1].ConvID) 69 } 70 select { 71 case <-chatUI.InboxLayoutCb: 72 require.Fail(t, "unexpected layout") 73 default: 74 } 75 76 // no layout is expected here, since the conv is in the layout (since we created it) 77 t.Logf("resmsgID: %d", 78 mustPostLocalForTest(t, ctc, users[0], conv2, chat1.NewMessageBodyWithText(chat1.MessageText{ 79 Body: "HI", 80 }))) 81 select { 82 case <-chatUI.InboxLayoutCb: 83 require.Fail(t, "unexpected layout") 84 default: 85 } 86 mustPostLocalForTest(t, ctc, users[0], conv1, chat1.NewMessageBodyWithText(chat1.MessageText{ 87 Body: "HI", 88 })) 89 // just one here, since the local update gets this into the top slot (we might get a second, so wait 90 // for it a bit (there is a race between the layout sending up to UI, and remote notification coming 91 // in) 92 layout = recvLayout() 93 require.Equal(t, 2, len(layout.SmallTeams)) 94 require.Equal(t, conv1.Id.ConvIDStr(), layout.SmallTeams[0].ConvID) 95 require.Equal(t, conv2.Id.ConvIDStr(), layout.SmallTeams[1].ConvID) 96 select { 97 case layout = <-chatUI.InboxLayoutCb: 98 require.Equal(t, 2, len(layout.SmallTeams)) 99 require.Equal(t, conv1.Id.ConvIDStr(), layout.SmallTeams[0].ConvID) 100 require.Equal(t, conv2.Id.ConvIDStr(), layout.SmallTeams[1].ConvID) 101 case <-time.After(timeout): 102 // just don't care if we don't get anything 103 } 104 105 // just one here, since we are now on msg ID 3 106 t.Logf("resmsgID: %d", 107 mustPostLocalForTest(t, ctc, users[0], conv2, chat1.NewMessageBodyWithText(chat1.MessageText{ 108 Body: "HI", 109 }))) 110 layout = recvLayout() 111 require.Equal(t, 2, len(layout.SmallTeams)) 112 require.Equal(t, conv2.Id.ConvIDStr(), layout.SmallTeams[0].ConvID) 113 require.Equal(t, conv1.Id.ConvIDStr(), layout.SmallTeams[1].ConvID) 114 select { 115 case layout = <-chatUI.InboxLayoutCb: 116 require.Equal(t, 2, len(layout.SmallTeams)) 117 require.Equal(t, conv2.Id.ConvIDStr(), layout.SmallTeams[0].ConvID) 118 require.Equal(t, conv1.Id.ConvIDStr(), layout.SmallTeams[1].ConvID) 119 case <-time.After(timeout): 120 // just don't care if we don't get anything 121 } 122 _, err := ctc.as(t, users[0]).chatLocalHandler().SetConversationStatusLocal(ctx, 123 chat1.SetConversationStatusLocalArg{ 124 ConversationID: conv1.Id, 125 Status: chat1.ConversationStatus_IGNORED, 126 }) 127 require.NoError(t, err) 128 // get two here 129 for i := 0; i < 2; i++ { 130 layout = recvLayout() 131 require.Equal(t, 1, len(layout.SmallTeams)) 132 require.Equal(t, conv2.Id.ConvIDStr(), layout.SmallTeams[0].ConvID) 133 } 134 select { 135 case <-chatUI.InboxLayoutCb: 136 require.Fail(t, "unexpected layout") 137 default: 138 } 139 140 t.Logf("big teams") 141 teamConv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 142 chat1.ConversationMembersType_TEAM, users[1], users[2]) 143 layout = recvLayout() 144 require.Equal(t, 2, len(layout.SmallTeams)) 145 require.Equal(t, teamConv.Id.ConvIDStr(), layout.SmallTeams[0].ConvID) 146 require.True(t, layout.SmallTeams[0].IsTeam) 147 require.Equal(t, conv2.Id.ConvIDStr(), layout.SmallTeams[1].ConvID) 148 topicName := "mike" 149 channel, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, 150 chat1.NewConversationLocalArg{ 151 TlfName: teamConv.TlfName, 152 TopicName: &topicName, 153 TopicType: chat1.TopicType_CHAT, 154 TlfVisibility: keybase1.TLFVisibility_PRIVATE, 155 MembersType: chat1.ConversationMembersType_TEAM, 156 }) 157 require.NoError(t, err) 158 159 layout = consumeAllLayout() 160 dat, _ := json.Marshal(layout) 161 t.Logf("LAYOUT: %s", string(dat)) 162 require.Equal(t, 1, len(layout.SmallTeams)) 163 require.Equal(t, conv2.Id.ConvIDStr(), layout.SmallTeams[0].ConvID) 164 require.Equal(t, 3, len(layout.BigTeams)) 165 st, err := layout.BigTeams[0].State() 166 require.NoError(t, err) 167 require.Equal(t, chat1.UIInboxBigTeamRowTyp_LABEL, st) 168 require.Equal(t, teamConv.TlfName, layout.BigTeams[0].Label().Name) 169 require.Equal(t, teamConv.Triple.Tlfid.TLFIDStr(), layout.BigTeams[0].Label().Id) 170 st, err = layout.BigTeams[1].State() 171 require.NoError(t, err) 172 require.Equal(t, chat1.UIInboxBigTeamRowTyp_CHANNEL, st) 173 require.Equal(t, teamConv.Id.ConvIDStr(), layout.BigTeams[1].Channel().ConvID) 174 require.Equal(t, teamConv.TlfName, layout.BigTeams[1].Channel().Teamname) 175 st, err = layout.BigTeams[2].State() 176 require.NoError(t, err) 177 require.Equal(t, chat1.UIInboxBigTeamRowTyp_CHANNEL, st) 178 require.Equal(t, channel.Conv.GetConvID().ConvIDStr(), layout.BigTeams[2].Channel().ConvID) 179 require.Equal(t, teamConv.TlfName, layout.BigTeams[2].Channel().Teamname) 180 require.Equal(t, topicName, layout.BigTeams[2].Channel().Channelname) 181 } 182 183 func TestUIInboxLoaderReselect(t *testing.T) { 184 useRemoteMock = false 185 defer func() { useRemoteMock = true }() 186 ctc := makeChatTestContext(t, "TestUIInboxLoaderReselect", 2) 187 defer ctc.cleanup() 188 timeout := 2 * time.Second 189 190 users := ctc.users() 191 chatUI := kbtest.NewChatUI() 192 ctx := ctc.as(t, users[0]).startCtx 193 tc := ctc.world.Tcs[users[0].Username] 194 uid := gregor1.UID(users[0].GetUID().ToBytes()) 195 tc.G.UIRouter = kbtest.NewMockUIRouter(chatUI) 196 tc.ChatG.UIInboxLoader = NewUIInboxLoader(tc.Context()) 197 tc.ChatG.UIInboxLoader.Start(ctx, uid) 198 defer func() { <-tc.ChatG.UIInboxLoader.Stop(ctx) }() 199 tc.ChatG.UIInboxLoader.(*UIInboxLoader).testingLayoutForceMode = true 200 tc.ChatG.UIInboxLoader.(*UIInboxLoader).batchDelay = time.Hour 201 202 recvLayout := func() chat1.UIInboxLayout { 203 select { 204 case layout := <-chatUI.InboxLayoutCb: 205 return layout 206 case <-time.After(timeout): 207 require.Fail(t, "no layout received") 208 } 209 return chat1.UIInboxLayout{} 210 } 211 consumeAllLayout := func() chat1.UIInboxLayout { 212 var layout chat1.UIInboxLayout 213 for { 214 select { 215 case layout = <-chatUI.InboxLayoutCb: 216 case <-time.After(timeout): 217 return layout 218 } 219 } 220 } 221 222 var layout chat1.UIInboxLayout 223 conv1 := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, 224 chat1.ConversationMembersType_TEAM, users[1]) 225 for i := 0; i < 2; i++ { 226 layout = recvLayout() 227 require.Equal(t, 1, len(layout.SmallTeams)) 228 require.Equal(t, conv1.Id.ConvIDStr(), layout.SmallTeams[0].ConvID) 229 } 230 tc.Context().Syncer.SelectConversation(ctx, conv1.Id) 231 232 topicName := "mike" 233 channel, err := ctc.as(t, users[0]).chatLocalHandler().NewConversationLocal(ctx, 234 chat1.NewConversationLocalArg{ 235 TlfName: conv1.TlfName, 236 TopicType: chat1.TopicType_CHAT, 237 TopicName: &topicName, 238 MembersType: chat1.ConversationMembersType_TEAM, 239 }) 240 require.NoError(t, err) 241 242 // there is a race where sometimes we need a third or fourth of these 243 layout = consumeAllLayout() 244 require.Nil(t, layout.ReselectInfo) 245 require.Equal(t, 3, len(layout.BigTeams)) 246 st, err := layout.BigTeams[0].State() 247 require.NoError(t, err) 248 require.Equal(t, chat1.UIInboxBigTeamRowTyp_LABEL, st) 249 require.Equal(t, conv1.TlfName, layout.BigTeams[0].Label().Name) 250 require.Equal(t, conv1.Triple.Tlfid.TLFIDStr(), layout.BigTeams[0].Label().Id) 251 st, err = layout.BigTeams[1].State() 252 require.NoError(t, err) 253 require.Equal(t, chat1.UIInboxBigTeamRowTyp_CHANNEL, st) 254 require.Equal(t, conv1.Id.ConvIDStr(), layout.BigTeams[1].Channel().ConvID) 255 require.Equal(t, conv1.TlfName, layout.BigTeams[1].Channel().Teamname) 256 st, err = layout.BigTeams[2].State() 257 require.NoError(t, err) 258 require.Equal(t, chat1.UIInboxBigTeamRowTyp_CHANNEL, st) 259 require.Equal(t, channel.Conv.GetConvID().ConvIDStr(), layout.BigTeams[2].Channel().ConvID) 260 require.Equal(t, conv1.TlfName, layout.BigTeams[2].Channel().Teamname) 261 require.Equal(t, topicName, layout.BigTeams[2].Channel().Channelname) 262 263 select { 264 case <-chatUI.InboxLayoutCb: 265 require.Fail(t, "unexpected layout") 266 default: 267 } 268 tc.Context().Syncer.SelectConversation(ctx, channel.Conv.GetConvID()) 269 _, err = ctc.as(t, users[0]).chatLocalHandler().DeleteConversationLocal(ctx, 270 chat1.DeleteConversationLocalArg{ 271 ConvID: channel.Conv.GetConvID(), 272 ChannelName: channel.Conv.GetTopicName(), 273 Confirmed: true, 274 }) 275 require.NoError(t, err) 276 277 layout = consumeAllLayout() 278 require.Equal(t, 1, len(layout.SmallTeams)) 279 require.Zero(t, len(layout.BigTeams)) 280 require.NotNil(t, layout.ReselectInfo) 281 require.NotNil(t, layout.ReselectInfo.NewConvID) 282 require.Equal(t, conv1.Id.ConvIDStr(), *layout.ReselectInfo.NewConvID) 283 }