import { describe, expect, it } from 'vitest' import { join, resolve } from 'path' import { repoRootConfigFromCommonDir, developWorktreePath, decideConfigResolution, } from '../config.js' describe('repoRootConfigFromCommonDir', () => { // The config lives at /.gitflow/config.json, where the repo root is // the PARENT of the git common dir. Fixtures are made absolute via resolve() so // the assertion is OS-independent (Windows drive letters included). it('points to /.gitflow/config.json for an absolute common dir, from any worktree', () => { const root = resolve('fixture-proj') const commonDir = join(root, '.git') // Same answer whether we ask from main, develop or a feature worktree. for (const wt of ['main', 'develop', join('features', 'feat-x')]) { expect(repoRootConfigFromCommonDir(commonDir, join(root, wt))).toBe( join(root, '.gitflow', 'config.json'), ) } }) it('resolves a RELATIVE common dir (git can return ".git") against cwd', () => { const cwd = resolve('fixture-rel') // dirname(resolve(cwd, '.git')) === cwd → /.gitflow/config.json expect(repoRootConfigFromCommonDir('.git', cwd)).toBe(join(cwd, '.gitflow', 'config.json')) }) }) describe('developWorktreePath', () => { const block = (path: string, branch?: string) => `worktree ${path}\nHEAD 0000000\n${branch ? `branch refs/heads/${branch}` : 'detached'}\n` it('returns the path of the worktree checked out on develop', () => { const porcelain = [block('/repo/main', 'main'), block('/repo/develop', 'develop')].join('\n') expect(developWorktreePath(porcelain)).toBe('/repo/develop') }) it('finds develop even amid feature/release worktrees', () => { const porcelain = [ block('/repo/main', 'main'), block('/repo/features/feat-x', 'feature/feat-x'), block('/repo/develop', 'develop'), block('/repo/releases/1.2.0', 'release/1.2.0'), ].join('\n') expect(developWorktreePath(porcelain)).toBe('/repo/develop') }) it('returns null when no worktree is on develop', () => { const porcelain = [block('/repo/main', 'main'), block('/repo/hotfix', 'hotfix/x')].join('\n') expect(developWorktreePath(porcelain)).toBeNull() }) it('ignores a detached worktree (no branch line)', () => { expect(developWorktreePath(block('/repo/detached'))).toBeNull() }) }) describe('decideConfigResolution', () => { const ROOT = '/repo/.gitflow/config.json' const LEGACY = '/repo/develop/.claude/gitflow/config.json' it('reads the root config when it already exists (canonical wins)', () => { expect(decideConfigResolution({ rootPath: ROOT, rootExists: true, legacyPath: LEGACY })).toEqual({ action: 'root', path: ROOT, }) }) it('migrates legacy → root when only the legacy config exists', () => { expect(decideConfigResolution({ rootPath: ROOT, rootExists: false, legacyPath: LEGACY })).toEqual({ action: 'migrate', from: LEGACY, to: ROOT, }) }) it('falls back to the legacy path when the root is not computable', () => { expect(decideConfigResolution({ rootPath: null, rootExists: false, legacyPath: LEGACY })).toEqual({ action: 'legacy', path: LEGACY, }) }) it('returns none when neither root nor legacy config is found', () => { expect(decideConfigResolution({ rootPath: ROOT, rootExists: false, legacyPath: null })).toEqual({ action: 'none', }) }) })