Files
orca/mobile/src/source-control/mobile-git-status.test.ts
T
Kelvin Amoaba f7047a8fc7 feat(mobile): redesigned Changes tab with file-first rows and swipe to discard (#37214)
Rows lead with the file name and put its folder under it, so names stop
truncating. The whole row opens the diff, one quiet button stages or
unstages, and discard moves to a left swipe that still asks first. A
status line, a Commits row and a conflict banner with Abort sit above
Uncommitted and Staged sections that carry Stage all and Unstage all.
The rarer git actions move into the header's grouped menu.
2026-11-01 23:17:10 +00:00

129 lines
4.2 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
buildMobileSourceControlSections,
canOpenMobileGitStatusEntry,
countStagedEntries,
countUnstagedEntries,
getStageablePaths,
getUnstageablePaths,
isMobileGitDiscardableEntry,
isMobileGitStageableEntry,
isMobileGitTransientRefreshError,
isMobileGitUnavailable,
type MobileGitStatusEntry
} from './mobile-git-status'
const entries: MobileGitStatusEntry[] = [
{ path: 'b.ts', status: 'modified', area: 'staged' },
{ path: 'a.ts', status: 'modified', area: 'unstaged' },
{ path: 'new.ts', status: 'untracked', area: 'untracked' }
]
describe('mobile source control status helpers', () => {
it('builds sections in the mobile source control order', () => {
const sections = buildMobileSourceControlSections(entries, 'changes-first')
expect(sections.map((section) => section.title)).toEqual(['Uncommitted', 'Staged'])
})
it('computes actionable path sets', () => {
expect(countUnstagedEntries(entries)).toBe(2)
expect(countStagedEntries(entries)).toBe(1)
expect(getStageablePaths(entries)).toEqual(['a.ts', 'new.ts'])
expect(getUnstageablePaths(entries)).toEqual(['b.ts'])
})
it('keeps unresolved conflicts out of stage actions', () => {
const conflictedEntries: MobileGitStatusEntry[] = [
{ path: 'ready.ts', status: 'modified', area: 'unstaged' },
{
path: 'conflicted.ts',
status: 'modified',
area: 'unstaged',
conflictStatus: 'unresolved'
},
{
path: 'resolved.ts',
status: 'modified',
area: 'unstaged',
conflictStatus: 'resolved_locally'
}
]
expect(getStageablePaths(conflictedEntries)).toEqual(['ready.ts', 'resolved.ts'])
expect(isMobileGitStageableEntry(conflictedEntries[1])).toBe(false)
expect(isMobileGitDiscardableEntry(conflictedEntries[1])).toBe(false)
expect(isMobileGitDiscardableEntry(conflictedEntries[2])).toBe(false)
})
it('allows opening deleted files so pre-delete text/image diffs can load', () => {
expect(
canOpenMobileGitStatusEntry({ path: 'deleted.png', status: 'deleted', area: 'unstaged' })
).toBe(true)
expect(
canOpenMobileGitStatusEntry({ path: 'logo.png', status: 'modified', area: 'unstaged' })
).toBe(true)
expect(
canOpenMobileGitStatusEntry({
path: 'conflict.ts',
status: 'modified',
area: 'unstaged',
conflictStatus: 'unresolved'
})
).toBe(false)
expect(
canOpenMobileGitStatusEntry({
path: 'resolved.ts',
status: 'modified',
area: 'unstaged',
conflictStatus: 'resolved_locally'
})
).toBe(true)
})
it('pins conflicts into their own section ahead of changes, like desktop', () => {
const sections = buildMobileSourceControlSections(
[
{ path: 'zeta.ts', status: 'modified', area: 'unstaged' },
{
path: 'beta.ts',
status: 'modified',
area: 'unstaged',
conflictStatus: 'resolved_locally'
},
{
path: 'alpha.ts',
status: 'modified',
area: 'unstaged',
conflictStatus: 'unresolved'
},
{ path: 'aardvark.ts', status: 'added', area: 'unstaged' }
],
'changes-first'
)
expect(
sections.map((section) => [section.title, section.data.map((entry) => entry.path)])
).toEqual([
['Conflicts', ['alpha.ts', 'beta.ts']],
['Uncommitted', ['aardvark.ts', 'zeta.ts']]
])
})
it('recognizes old-desktop unavailable responses', () => {
expect(isMobileGitUnavailable('forbidden', 'Method is not available to mobile clients')).toBe(
true
)
expect(isMobileGitUnavailable('method_not_found', 'Unknown method')).toBe(true)
expect(isMobileGitUnavailable('bad_request', 'Missing worktree selector')).toBe(false)
})
it('recognizes transient status refresh aborts', () => {
expect(isMobileGitTransientRefreshError('runtime_error', 'Aborting')).toBe(true)
expect(isMobileGitTransientRefreshError('request_aborted', 'request_aborted')).toBe(true)
expect(isMobileGitTransientRefreshError('runtime_error', 'fatal: not a git repository')).toBe(
false
)
})
})