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  }