@@ -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,39 @@ 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+ // Picker is disabled here to mirror production (host binds modelPickerEnabled to chatId == null).
581+ whenever(modelManager.getSelectedModelId()).thenReturn(" global-model" )
582+ val modelStateFlow = MutableStateFlow (
583+ ModelState (
584+ models = listOf (aiModel(id = " other-model" , supported = listOf (ReasoningEffort .NONE ))),
585+ ),
586+ )
587+ whenever(modelManager.modelState).thenReturn(modelStateFlow)
588+ whenever(duckAiChatStore.getChatById(" chat-1" )).thenReturn(
589+ DuckAiChat (chatId = " chat-1" , title = " t" , model = " missing-model" , lastEdit = " now" , pinned = false ),
590+ )
591+ val viewModel = createViewModel()
592+ viewModel.configure(tabId = " tab-A" , isDuckAiMode = true , isBottom = false )
593+ viewModel.setActiveChatId(" chat-1" )
594+ viewModel.setModelPickerEnabled(false )
595+ advanceUntilIdle()
596+
597+ assertEquals(" global-model" , viewModel.getSelectedModelId())
598+ }
599+
572600 @Test
573601 fun whenChatIdSetAndPickerDisabledThenGetSelectedModelIdStillReturnsChatsModel () = runTest {
574602 // Production binds modelPickerEnabled to `chatId == null`, so existing chats always have the
575603 // picker disabled. Submission must still carry the chat's stored model.
576604 whenever(modelManager.getSelectedModelId()).thenReturn(" global-model" )
605+ val modelStateFlow = MutableStateFlow (
606+ ModelState (models = listOf (aiModel(id = " chat-model" , supported = listOf (ReasoningEffort .NONE )))),
607+ )
608+ whenever(modelManager.modelState).thenReturn(modelStateFlow)
577609 whenever(duckAiChatStore.getChatById(" chat-1" )).thenReturn(
578610 DuckAiChat (chatId = " chat-1" , title = " t" , model = " chat-model" , lastEdit = " now" , pinned = false ),
579611 )
@@ -603,6 +635,15 @@ class NativeInputModeWidgetViewModelTest {
603635 // for chat-B is suspended. Submission must fall back to global rather than return chat-A's
604636 // model tagged to chat-B.
605637 whenever(modelManager.getSelectedModelId()).thenReturn(" global-model" )
638+ val modelStateFlow = MutableStateFlow (
639+ ModelState (
640+ models = listOf (
641+ aiModel(id = " chat-A-model" , supported = listOf (ReasoningEffort .NONE )),
642+ aiModel(id = " chat-B-model" , supported = listOf (ReasoningEffort .NONE )),
643+ ),
644+ ),
645+ )
646+ whenever(modelManager.modelState).thenReturn(modelStateFlow)
606647 whenever(duckAiChatStore.getChatById(" chat-A" )).thenReturn(
607648 DuckAiChat (chatId = " chat-A" , title = " t" , model = " chat-A-model" , lastEdit = " now" , pinned = false ),
608649 )
@@ -634,6 +675,10 @@ class NativeInputModeWidgetViewModelTest {
634675 // setActiveChatId nulls currentChat synchronously and launches the lookup. During the in-
635676 // flight window, submission must fall back to global rather than return chat-A's data.
636677 whenever(modelManager.getSelectedModelId()).thenReturn(" global-model" )
678+ val modelStateFlow = MutableStateFlow (
679+ ModelState (models = listOf (aiModel(id = " chat-A-model" , supported = listOf (ReasoningEffort .NONE )))),
680+ )
681+ whenever(modelManager.modelState).thenReturn(modelStateFlow)
637682 whenever(duckAiChatStore.getChatById(" chat-A" )).thenReturn(
638683 DuckAiChat (chatId = " chat-A" , title = " t" , model = " chat-A-model" , lastEdit = " now" , pinned = false ),
639684 )
@@ -1092,6 +1137,21 @@ class NativeInputModeWidgetViewModelTest {
10921137 assertNull(nativeInputStateProvider.stateForTab(tabId).value.chatId)
10931138 }
10941139
1140+ @Test
1141+ fun whenSetActiveChatIdBeforeConfigureThenChatIdIsBufferedAndPublishedOnConfigure () = runTest {
1142+ // Defensive: setActiveChatId fires before activeTabId is set. The pending value must be
1143+ // replayed once configure runs.
1144+ val freshViewModel = createViewModel()
1145+
1146+ freshViewModel.setActiveChatId(" chat-123" )
1147+ advanceUntilIdle()
1148+
1149+ freshViewModel.configure(tabId = " tab-A" , isDuckAiMode = false , isBottom = false )
1150+ advanceUntilIdle()
1151+
1152+ assertEquals(" chat-123" , nativeInputStateProvider.stateForTab(" tab-A" ).value.chatId)
1153+ }
1154+
10951155 // endregion
10961156
10971157 // region fireChatHistorySelectedPixel
0 commit comments