@@ -558,6 +558,10 @@ class NativeInputModeWidgetViewModelTest {
558558 @Test
559559 fun whenChatIdSetThenGetSelectedModelIdReturnsChatsModel () = runTest {
560560 whenever(modelManager.getSelectedModelId()).thenReturn(" global-model" )
561+ val modelStateFlow = MutableStateFlow (
562+ ModelState (models = listOf (aiModel(id = " chat-model" , supported = listOf (ReasoningEffort .NONE )))),
563+ )
564+ whenever(modelManager.modelState).thenReturn(modelStateFlow)
561565 whenever(duckAiChatStore.getChatById(" chat-1" )).thenReturn(
562566 DuckAiChat (chatId = " chat-1" , title = " t" , model = " chat-model" , lastEdit = " now" , pinned = false ),
563567 )
@@ -569,11 +573,37 @@ class NativeInputModeWidgetViewModelTest {
569573 assertEquals(" chat-model" , viewModel.getSelectedModelId())
570574 }
571575
576+ @Test
577+ fun whenChatModelNotInModelsListThenGetSelectedModelIdFallsBackToGlobal () = runTest {
578+ // Chat references "missing-model" no longer offered server-side. Submission must fall back to
579+ // global on both halves — chat-model + global-effort would be a mismatched pair.
580+ whenever(modelManager.getSelectedModelId()).thenReturn(" global-model" )
581+ val modelStateFlow = MutableStateFlow (
582+ ModelState (
583+ models = listOf (aiModel(id = " other-model" , supported = listOf (ReasoningEffort .NONE ))),
584+ ),
585+ )
586+ whenever(modelManager.modelState).thenReturn(modelStateFlow)
587+ whenever(duckAiChatStore.getChatById(" chat-1" )).thenReturn(
588+ DuckAiChat (chatId = " chat-1" , title = " t" , model = " missing-model" , lastEdit = " now" , pinned = false ),
589+ )
590+ val viewModel = createViewModel()
591+ viewModel.configure(tabId = " tab-A" , isDuckAiMode = true , isBottom = false )
592+ viewModel.setActiveChatId(" chat-1" )
593+ advanceUntilIdle()
594+
595+ assertEquals(" global-model" , viewModel.getSelectedModelId())
596+ }
597+
572598 @Test
573599 fun whenChatIdSetAndPickerDisabledThenGetSelectedModelIdStillReturnsChatsModel () = runTest {
574600 // Production binds modelPickerEnabled to `chatId == null`, so existing chats always have the
575601 // picker disabled. Submission must still carry the chat's stored model.
576602 whenever(modelManager.getSelectedModelId()).thenReturn(" global-model" )
603+ val modelStateFlow = MutableStateFlow (
604+ ModelState (models = listOf (aiModel(id = " chat-model" , supported = listOf (ReasoningEffort .NONE )))),
605+ )
606+ whenever(modelManager.modelState).thenReturn(modelStateFlow)
577607 whenever(duckAiChatStore.getChatById(" chat-1" )).thenReturn(
578608 DuckAiChat (chatId = " chat-1" , title = " t" , model = " chat-model" , lastEdit = " now" , pinned = false ),
579609 )
@@ -603,6 +633,15 @@ class NativeInputModeWidgetViewModelTest {
603633 // for chat-B is suspended. Submission must fall back to global rather than return chat-A's
604634 // model tagged to chat-B.
605635 whenever(modelManager.getSelectedModelId()).thenReturn(" global-model" )
636+ val modelStateFlow = MutableStateFlow (
637+ ModelState (
638+ models = listOf (
639+ aiModel(id = " chat-A-model" , supported = listOf (ReasoningEffort .NONE )),
640+ aiModel(id = " chat-B-model" , supported = listOf (ReasoningEffort .NONE )),
641+ ),
642+ ),
643+ )
644+ whenever(modelManager.modelState).thenReturn(modelStateFlow)
606645 whenever(duckAiChatStore.getChatById(" chat-A" )).thenReturn(
607646 DuckAiChat (chatId = " chat-A" , title = " t" , model = " chat-A-model" , lastEdit = " now" , pinned = false ),
608647 )
@@ -634,6 +673,10 @@ class NativeInputModeWidgetViewModelTest {
634673 // setActiveChatId nulls currentChat synchronously and launches the lookup. During the in-
635674 // flight window, submission must fall back to global rather than return chat-A's data.
636675 whenever(modelManager.getSelectedModelId()).thenReturn(" global-model" )
676+ val modelStateFlow = MutableStateFlow (
677+ ModelState (models = listOf (aiModel(id = " chat-A-model" , supported = listOf (ReasoningEffort .NONE )))),
678+ )
679+ whenever(modelManager.modelState).thenReturn(modelStateFlow)
637680 whenever(duckAiChatStore.getChatById(" chat-A" )).thenReturn(
638681 DuckAiChat (chatId = " chat-A" , title = " t" , model = " chat-A-model" , lastEdit = " now" , pinned = false ),
639682 )
@@ -1092,6 +1135,21 @@ class NativeInputModeWidgetViewModelTest {
10921135 assertNull(nativeInputStateProvider.stateForTab(tabId).value.chatId)
10931136 }
10941137
1138+ @Test
1139+ fun whenSetActiveChatIdBeforeConfigureThenChatIdIsBufferedAndPublishedOnConfigure () = runTest {
1140+ // Defensive: setActiveChatId fires before activeTabId is set. The pending value must be
1141+ // replayed once configure runs.
1142+ val freshViewModel = createViewModel()
1143+
1144+ freshViewModel.setActiveChatId(" chat-123" )
1145+ advanceUntilIdle()
1146+
1147+ freshViewModel.configure(tabId = " tab-A" , isDuckAiMode = false , isBottom = false )
1148+ advanceUntilIdle()
1149+
1150+ assertEquals(" chat-123" , nativeInputStateProvider.stateForTab(" tab-A" ).value.chatId)
1151+ }
1152+
10951153 // endregion
10961154
10971155 // region fireChatHistorySelectedPixel
0 commit comments