import { describe, expect, it } from 'vitest' import { assertBranchTarget, getBaseBranch, getTargetBranch, normalizeNameForType, normalizeReleaseName, buildFullBranchName, } from '../branch.js' import type { GitFlowConfig } from '../types.js' function cfg(main = 'main', develop = 'develop'): GitFlowConfig { return { git: { branches: { main, develop }, prefixes: { feature: 'feature/', release: 'release/', hotfix: 'hotfix/' }, }, } as unknown as GitFlowConfig } describe('getBaseBranch', () => { it('release branches FROM develop (4.x rule)', () => { expect(getBaseBranch('release', cfg())).toBe('develop') }) it('feature from develop, hotfix from main', () => { expect(getBaseBranch('feature', cfg())).toBe('develop') expect(getBaseBranch('hotfix', cfg())).toBe('main') }) }) describe('getTargetBranch', () => { it('feature → develop, release/hotfix → main', () => { expect(getTargetBranch('feature', cfg())).toBe('develop') expect(getTargetBranch('release', cfg())).toBe('main') expect(getTargetBranch('hotfix', cfg())).toBe('main') }) }) describe('assertBranchTarget — feature→main guard (incident #451)', () => { it('throws when a feature targets main', () => { expect(() => assertBranchTarget('feature', 'main', cfg())).toThrow() }) it('throws when a feature targets master', () => { expect(() => assertBranchTarget('feature', 'master', cfg('master'))).toThrow() }) it('throws when develop is empty (the #451 root cause)', () => { expect(() => assertBranchTarget('feature', '', cfg('main', ''))).toThrow() }) it('throws when develop === main', () => { expect(() => assertBranchTarget('feature', 'main', cfg('main', 'main'))).toThrow() }) it('allows a feature → develop', () => { expect(() => assertBranchTarget('feature', 'develop', cfg())).not.toThrow() }) it('allows release/hotfix → main', () => { expect(() => assertBranchTarget('release', 'main', cfg())).not.toThrow() expect(() => assertBranchTarget('hotfix', 'main', cfg())).not.toThrow() }) }) describe('per-type name normalization', () => { it('keeps version dots for release/hotfix (3.53.0 stays 3.53.0)', () => { expect(normalizeNameForType('3.53.0', 'release')).toBe('3.53.0') expect(normalizeNameForType('3.53.1', 'hotfix')).toBe('3.53.1') expect(normalizeReleaseName(' v1.20.0 ')).toBe('v1.20.0') }) it('kebab-cases feature names (dots/accents stripped)', () => { expect(normalizeNameForType('Mon Idée!', 'feature')).toBe('mon-idee') }) it('builds the full branch with the right prefix and dots intact', () => { expect(buildFullBranchName(normalizeNameForType('3.53.0', 'release'), 'release', cfg())).toBe('release/3.53.0') expect(buildFullBranchName(normalizeNameForType('User Auth', 'feature'), 'feature', cfg())).toBe('feature/user-auth') }) })