1+ import * as fc from "fast-check"
12import { describe , expect , it } from "vitest"
23
34import { projectTerminalLabel } from "../../src/core/project-terminal-label.js"
45
6+ const asciiCodeToCharacter = ( code : number ) : string => String . fromCodePoint ( code )
7+
8+ const alphaNumericCharacterArbitrary = fc . oneof (
9+ fc . integer ( { max : 57 , min : 48 } ) ,
10+ fc . integer ( { max : 90 , min : 65 } ) ,
11+ fc . integer ( { max : 122 , min : 97 } )
12+ ) . map ( ( code ) => asciiCodeToCharacter ( code ) )
13+
14+ const pathCharacterArbitrary = fc . oneof ( alphaNumericCharacterArbitrary , fc . constant ( "-" ) )
15+
16+ const labelCharacterArbitrary = fc . oneof (
17+ pathCharacterArbitrary ,
18+ fc . constant ( "_" ) ,
19+ fc . constant ( "." ) ,
20+ fc . constant ( "/" )
21+ )
22+
23+ const gitHubPathSegmentArbitrary = fc . tuple (
24+ alphaNumericCharacterArbitrary ,
25+ fc . array ( pathCharacterArbitrary , { maxLength : 12 } )
26+ ) . map ( ( [ head , tail ] ) => `${ head } ${ tail . join ( "" ) } ` )
27+
28+ const readableLabelArbitrary = fc . array ( labelCharacterArbitrary , {
29+ maxLength : 24 ,
30+ minLength : 1
31+ } ) . map ( ( characters ) => characters . join ( "" ) )
32+
33+ const paddedReadableLabelArbitrary = fc . tuple (
34+ fc . constantFrom ( "" , " " , " " ) ,
35+ readableLabelArbitrary ,
36+ fc . constantFrom ( "" , " " , " " )
37+ ) . map ( ( [ left , value , right ] ) => `${ left } ${ value } ${ right } ` )
38+
39+ const repositoryArbitrary = fc . record ( {
40+ owner : gitHubPathSegmentArbitrary ,
41+ repo : gitHubPathSegmentArbitrary
42+ } )
43+
44+ type GeneratedRepository = {
45+ readonly owner : string
46+ readonly repo : string
47+ }
48+
49+ const refIdArbitrary = fc . integer ( { max : 1_000_000 , min : 1 } )
50+
51+ const assertRepositoryRefIdProperty = (
52+ assertion : ( repository : GeneratedRepository , refId : number ) => void
53+ ) : void => {
54+ fc . assert ( fc . property ( repositoryArbitrary , refIdArbitrary , assertion ) )
55+ }
56+
557describe ( "projectTerminalLabel" , ( ) => {
658 it ( "renders GitHub issue source context and container identity" , ( ) => {
759 expect ( projectTerminalLabel ( {
@@ -28,4 +80,85 @@ describe("projectTerminalLabel", () => {
2880 repoUrl : "https://github.com/org/repo.git"
2981 } ) ) . toBe ( "org/repo | source https://github.com/org/repo.git (feature-x)" )
3082 } )
83+
84+ it ( "preserves issue markers and GitHub issue URLs for generated issue refs" , ( ) => {
85+ assertRepositoryRefIdProperty ( ( { owner, repo } , issueId ) => {
86+ const label = projectTerminalLabel ( {
87+ displayName : `${ owner } /${ repo } ` ,
88+ repoRef : `issue-${ issueId } ` ,
89+ repoUrl : `https://github.com/${ owner } /${ repo } .git`
90+ } )
91+
92+ expect ( label ) . toBe (
93+ `${ owner } /${ repo } | issue #${ issueId } (https://github.com/${ owner } /${ repo } /issues/${ issueId } )`
94+ )
95+ } )
96+ } )
97+
98+ it ( "preserves PR and MR markers for generated review refs" , ( ) => {
99+ fc . assert (
100+ fc . property (
101+ repositoryArbitrary ,
102+ refIdArbitrary ,
103+ fc . constantFrom ( "pull" , "merge-request" ) ,
104+ ( { owner, repo } , reviewId , refKind ) => {
105+ const repoUrl = `git@github.com:${ owner } /${ repo } .git`
106+ const label = projectTerminalLabel ( {
107+ displayName : `${ owner } /${ repo } ` ,
108+ repoRef : refKind === "pull" ? `refs/pull/${ reviewId } /head` : `refs/merge-requests/${ reviewId } /head` ,
109+ repoUrl
110+ } )
111+
112+ expect ( label ) . toBe (
113+ refKind === "pull"
114+ ? `${ owner } /${ repo } | PR #${ reviewId } (https://github.com/${ owner } /${ repo } /pull/${ reviewId } )`
115+ : `${ owner } /${ repo } | MR #${ reviewId } `
116+ )
117+ }
118+ )
119+ )
120+ } )
121+
122+ it ( "uses repoUrl as the base label when displayName is blank" , ( ) => {
123+ fc . assert (
124+ fc . property ( repositoryArbitrary , fc . constantFrom ( "" , " " , " " ) , ( { owner, repo } , displayName ) => {
125+ const repoUrl = `https://github.com/${ owner } /${ repo } .git`
126+
127+ expect ( projectTerminalLabel ( {
128+ displayName,
129+ repoRef : "main" ,
130+ repoUrl
131+ } ) ) . toBe ( `${ repoUrl } | source ${ repoUrl } ` )
132+ } )
133+ )
134+ } )
135+
136+ it ( "normalizes empty and main refs to source context without ref suffix" , ( ) => {
137+ fc . assert (
138+ fc . property ( repositoryArbitrary , fc . constantFrom ( "" , " " , " " , "main" ) , ( { owner, repo } , repoRef ) => {
139+ const repoUrl = `https://github.com/${ owner } /${ repo } .git`
140+
141+ expect ( projectTerminalLabel ( {
142+ displayName : `${ owner } /${ repo } ` ,
143+ repoRef,
144+ repoUrl
145+ } ) ) . toBe ( `${ owner } /${ repo } | source ${ repoUrl } ` )
146+ } )
147+ )
148+ } )
149+
150+ it ( "preserves non-empty container names after trimming" , ( ) => {
151+ fc . assert (
152+ fc . property ( repositoryArbitrary , paddedReadableLabelArbitrary , ( { owner, repo } , containerName ) => {
153+ const label = projectTerminalLabel ( {
154+ containerName,
155+ displayName : `${ owner } /${ repo } ` ,
156+ repoRef : "feature-x" ,
157+ repoUrl : `https://github.com/${ owner } /${ repo } .git`
158+ } )
159+
160+ expect ( label . endsWith ( ` | container ${ containerName . trim ( ) } ` ) ) . toBe ( true )
161+ } )
162+ )
163+ } )
31164} )
0 commit comments