Files
claude-code-haha/desktop/src/components/mobile/mobileSessionStatus.test.ts
T
程序员阿江-Relakkes 4fb4784b81 feat(desktop): rebuild the H5 phone and tablet shell around touch (#1460)
Replace the drawer-based mobile layout with a single navigation stack. Home
is the session list grouped by status (waiting for you, working, then by
day) with the new-task composer docked under it; a session, Settings and
subagent or team-member pages push on top, and the system back gesture
moves up one level. Touch tablets keep the list beside the page.

- Composer: one row (+, model, send); permission mode, context usage and
  the capability menu move into a + bottom sheet.
- Approvals: a waiting permission, question or plan takes the composer's
  place; the transcript keeps a one-line marker. Denying can carry a reason.
- Messages: press and hold for copy, select text, edit and branch instead
  of an always-visible icon row.
- Tool runs fold into one card per run and open the full timeline in a
  sheet; the turn change card opens a file's diff full screen.
- Activity: a pill in the session bar opens the activity sheet instead of
  auto-opening over the chat.
- Settings: one grouped list with the connection, provider, phone-editable
  preferences and the computer-only settings shown read-only.
- Server: GET /api/sessions/live-status reports every running or waiting
  session so the list can sort sessions the phone never opened.
2026-10-06 03:18:30 +08:00

80 lines
2.6 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import type { Tab } from '../../stores/tabStore'
import { deriveMobileSessionStatus } from './mobileSessionStatus'
type ChatSource = Parameters<typeof deriveMobileSessionStatus>[1][string]
function chat(overrides: Partial<NonNullable<ChatSource>> = {}): NonNullable<ChatSource> {
return {
chatState: 'idle',
connectionState: 'connected',
pendingPermission: null,
pendingPermissions: undefined,
backgroundAgentTasks: undefined,
...overrides,
} as NonNullable<ChatSource>
}
const pending = {
requestId: 'permission-1',
toolName: 'Bash',
input: { command: 'bun run build' },
} as never
describe('deriveMobileSessionStatus', () => {
it('reads sessions nobody here connected to from the server answer', () => {
const status = deriveMobileSessionStatus(
[],
{},
new Map([['remote-waiting', 'waiting'], ['remote-running', 'running']]),
)
expect([...status.attentionIds]).toEqual(['remote-waiting'])
expect([...status.runningIds]).toEqual(['remote-running'])
})
it('trusts a live socket over a poll that has not caught up yet', () => {
// Answered on the phone a moment ago: the store has cleared the request,
// the last poll still says waiting. The row must not keep asking.
const status = deriveMobileSessionStatus(
[],
{ answered: chat({ chatState: 'idle' }) },
new Map([['answered', 'waiting']]),
)
expect(status.attentionIds.has('answered')).toBe(false)
expect(status.runningIds.has('answered')).toBe(false)
})
it('still uses the poll for a session whose socket has dropped', () => {
const status = deriveMobileSessionStatus(
[],
{ dropped: chat({ connectionState: 'reconnecting' }) },
new Map([['dropped', 'waiting']]),
)
expect(status.attentionIds.has('dropped')).toBe(true)
})
it('counts a connected session parked on a card as waiting, not working', () => {
const status = deriveMobileSessionStatus(
[{ sessionId: 'card', title: 'Card', type: 'session', status: 'running' } as Tab],
{ card: chat({ chatState: 'tool_executing', pendingPermission: pending }) },
new Map(),
)
expect(status.attentionIds.has('card')).toBe(true)
expect(status.runningIds.has('card')).toBe(false)
})
it('marks a tab the desktop reported running as working', () => {
const status = deriveMobileSessionStatus(
[{ sessionId: 'tab', title: 'Tab', type: 'session', status: 'running' } as Tab],
{},
new Map(),
)
expect(status.runningIds.has('tab')).toBe(true)
})
})