Skip to content

test(chat-ui): port 13 skipped ChatViewModel tests from v2026.3.11 (#2381)#2389

Merged
alexey-pelykh merged 1 commit intomainfrom
test/chat-viewmodel-tests-2381
Apr 17, 2026
Merged

test(chat-ui): port 13 skipped ChatViewModel tests from v2026.3.11 (#2381)#2389
alexey-pelykh merged 1 commit intomainfrom
test/chat-viewmodel-tests-2381

Conversation

@alexey-pelykh
Copy link
Copy Markdown

Summary

  • Ports 13 @Test functions from upstream commit 061b8258bc ("macOS: add chat model selector and persist thinking") that were skipped during the fork sync in sync: upstream to v2026.3.11 (235 commits) #2379.
  • Count clarification: Issue Port 9 skipped @Test functions in ChatViewModelTests.swift #2381 states "9 tests" but the actual v2026.3.8..v2026.3.11 cycle delta is 13 tests — verified via git log v2026.3.8..v2026.3.11 on upstream. All 13 ported.
  • Helper infrastructure ported (14 helpers). TestChatTransport extended with modelResponses + optional setSessionModelHook/setSessionThinkingHook params (backward-compatible) and 3 new async query methods.
  • Existing 11 tests preserved verbatim — no regressions.

Decision on contextTokens

Adapt tests to omit (not extend fork types).

Fork deliberately gutted contextTokens in #2277 ("gut(session): remove vestigial contextTokens config and display denominator"). Extending fork types would revert that decision. All ported tests drop the contextTokens: argument from RemoteClawChatSessionsDefaults and sessionEntry helper.

Ported tests

10 model-selection tests (RemoteClawChatViewModel.selectModel / modelSelectionID / modelChoices / patchedModels):

  • bootstrapsModelSelectionFromSessionAndDefaults
  • selectingDefaultModelPatchesNilAndUpdatesSelection
  • selectingProviderQualifiedModelDisambiguatesDuplicateModelIDs
  • slashModelIDsStayProviderQualifiedInSelectionAndPatch
  • staleModelPatchCompletionsDoNotOverwriteNewerSelection
  • sendWaitsForInFlightModelPatchToFinish
  • failedLatestModelSelectionDoesNotReplayAfterOlderCompletionFinishes
  • failedLatestModelSelectionRestoresEarlierSuccessWithoutReplay
  • switchingSessionsIgnoresLateModelPatchCompletionFromPreviousSession
  • lateModelCompletionDoesNotReplayCurrentSessionSelectionIntoPreviousSession

3 thinking-level tests (RemoteClawChatViewModel.selectThinkingLevel / initialThinkingLevel / onThinkingLevelChanged):

  • explicitThinkingLevelWinsOverHistoryAndPersistsChanges
  • serverProvidedThinkingLevelsOutsideMenuArePreservedForSend
  • staleThinkingPatchCompletionReappliesLatestSelection

Test plan

  • File compiles without introducing new Swift errors (test file itself has 0 errors — only pre-existing GatewayChannel.swift compile break remains).
  • @Test func count: 24 (11 existing + 13 new).
  • grep contextTokens → 0 matches (decision honored).
  • grep OpenClaw → 0 matches (rebrand complete).
  • grep resetSession|compactSession|mainSessionKey → 0 matches (fork lacks these features; none of the 13 ported tests require them).
  • Existing 11 test bodies preserved verbatim (diff shows only additions + 2-line delta in TestChatTransport).
  • Backward-compat preserved: existing TestChatTransport(historyResponses:) calls still compile.
  • Pre-commit hooks pass (format, lint, rebrand leakage, no-remoteclaw-ai).

Known limitation

swift test cannot be run locally due to a pre-existing compile break in RemoteClawKit/GatewayChannel.swift — references to symbols that were gutted during the v2026.3.11 sync (ThrowingContinuationSupport, GatewayDeviceAuthPayload, GatewayConnectChallengeSupport). Filed #2388 for that issue. Fork CI has no macOS/Swift jobs so this compile state exists independent of this PR.

Once #2388 lands, swift test --filter ChatViewModelTests will exercise all 24 tests (11 existing + 13 new).

Helper scaffolding note

6 of the 14 ported helpers (chatTextMessage, sendMessageAndEmitFinal, emitAssistantText, emitToolStart, emitExternalFinal, AsyncCounter) are not referenced by the 13 ported tests — they're used by upstream tests from later sync cycles (optimistic-message, reset/compact triggers) or by upstream's refactored versions of tests the fork preserves verbatim per AC4. Kept as scaffolding to preserve symmetry with upstream for future sync cycles.

References

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

Ports 13 @test functions from upstream commit 061b825 ("macOS: add
chat model selector and persist thinking") that were skipped during
the fork sync in #2379. Issue #2381 states "9 tests" but the actual
v2026.3.8..v2026.3.11 cycle delta is 13 — verified via git log.

Ten model-selection tests cover: bootstrap, default patch, provider-
qualified disambiguation, provider-qualified slash IDs, stale patch
race, send-waits-for-patch, failed-latest replay, failed-latest
restore, session-switch late patch, cross-session replay isolation.

Three thinking-level tests cover: explicit level persistence, server-
provided levels outside menu, stale patch replay.

Decision on contextTokens: adapt tests to omit. Fork gutted the field
in #2277 — extending types would revert that deliberate decision.

Helper infrastructure ported: 14 private helpers (functions, actors,
classes). TestChatTransport extended with modelResponses + optional
hook params (backward-compatible) and new async query methods.
Existing 11 tests preserved verbatim.

Local compile blocked by pre-existing symbol references in
RemoteClawKit/GatewayChannel.swift (see #2388). Fork CI has no Swift
jobs so this state also pre-exists and is orthogonal to this change.

Closes #2381.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Ports skipped upstream ChatViewModelTests coverage into the fork to validate model selection + thinking-level persistence behavior in RemoteClawChatViewModel.

Changes:

  • Added helper scaffolding to construct histories/sessions/models and drive RemoteClawChatViewModel in async tests.
  • Extended the in-file TestChatTransport to support model listing responses, session patch hooks, and additional introspection helpers.
  • Added 13 new @Test cases covering model selection edge-cases and thinking-level selection behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

await MainActor.run { vm.load() }
try await waitUntil("bootstrap") {
await MainActor.run {
vm.healthOK && (sessionId == nil || vm.sessionId == sessionId)
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadAndWaitBootstrap returns as soon as healthOK becomes true, but RemoteClawChatViewModel.bootstrap() sets healthOK before awaiting fetchSessions()/fetchModels(). The new model-selection tests assert on sessions/modelChoices immediately after this helper, so they can race/flap. Consider waiting for !vm.isLoading (or explicitly for vm.sessions/vm.modelChoices to be populated) in addition to healthOK/sessionId so the helper only returns once bootstrap has fully finished.

Suggested change
vm.healthOK && (sessionId == nil || vm.sessionId == sessionId)
vm.healthOK &&
!vm.isLoading &&
(sessionId == nil || vm.sessionId == sessionId)

Copilot uses AI. Check for mistakes.
Comment on lines +225 to +235
private var continuation: CheckedContinuation<Void, Never>?

func wait() async {
await withCheckedContinuation { continuation in
self.continuation = continuation
}
}

func open() {
self.continuation?.resume()
self.continuation = nil
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AsyncGate.open() is a no-op if it’s called before wait() stores the continuation. Because the model-patch task can be descheduled between appending patchedModels and entering the hook’s await gate.wait(), this can introduce a rare deadlock/flaky timeout in sendWaitsForInFlightModelPatchToFinish. Make AsyncGate resilient to out-of-order calls (e.g., track an isOpen flag so wait() returns immediately if already opened, and/or support multiple waiters).

Suggested change
private var continuation: CheckedContinuation<Void, Never>?
func wait() async {
await withCheckedContinuation { continuation in
self.continuation = continuation
}
}
func open() {
self.continuation?.resume()
self.continuation = nil
private var isOpen = false
private var continuations: [CheckedContinuation<Void, Never>] = []
func wait() async {
if self.isOpen {
return
}
await withCheckedContinuation { continuation in
if self.isOpen {
continuation.resume()
} else {
self.continuations.append(continuation)
}
}
}
func open() {
self.isOpen = true
let continuations = self.continuations
self.continuations.removeAll()
for continuation in continuations {
continuation.resume()
}

Copilot uses AI. Check for mistakes.
@alexey-pelykh alexey-pelykh merged commit b805e14 into main Apr 17, 2026
16 checks passed
@alexey-pelykh alexey-pelykh deleted the test/chat-viewmodel-tests-2381 branch April 17, 2026 16:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Port 9 skipped @Test functions in ChatViewModelTests.swift

2 participants