@@ -5089,8 +5089,9 @@ func TestTUIYankCopyFromReviewView(t *testing.T) {
50895089 t .Errorf ("Expected no error, got %v" , result .err )
50905090 }
50915091
5092- if mock .lastText != "This is the review content to copy" {
5093- t .Errorf ("Expected clipboard to contain review content, got %q" , mock .lastText )
5092+ expectedContent := "Review #1\n \n This is the review content to copy"
5093+ if mock .lastText != expectedContent {
5094+ t .Errorf ("Expected clipboard to contain review with header, got %q" , mock .lastText )
50945095 }
50955096}
50965097
@@ -5265,7 +5266,7 @@ func TestTUIFetchReviewAndCopySuccess(t *testing.T) {
52655266 m := newTuiModel (ts .URL )
52665267
52675268 // Execute fetchReviewAndCopy
5268- cmd := m .fetchReviewAndCopy (123 )
5269+ cmd := m .fetchReviewAndCopy (123 , nil )
52695270 msg := cmd ()
52705271
52715272 result , ok := msg .(tuiClipboardResultMsg )
@@ -5277,8 +5278,10 @@ func TestTUIFetchReviewAndCopySuccess(t *testing.T) {
52775278 t .Errorf ("Expected no error, got %v" , result .err )
52785279 }
52795280
5280- if mock .lastText != "Review content for clipboard" {
5281- t .Errorf ("Expected clipboard to contain review content, got %q" , mock .lastText )
5281+ // Clipboard should contain header + review content
5282+ expectedContent := "Review #1\n \n Review content for clipboard"
5283+ if mock .lastText != expectedContent {
5284+ t .Errorf ("Expected clipboard to contain review with header, got %q" , mock .lastText )
52825285 }
52835286}
52845287
@@ -5291,7 +5294,7 @@ func TestTUIFetchReviewAndCopy404(t *testing.T) {
52915294
52925295 m := newTuiModel (ts .URL )
52935296
5294- cmd := m .fetchReviewAndCopy (123 )
5297+ cmd := m .fetchReviewAndCopy (123 , nil )
52955298 msg := cmd ()
52965299
52975300 result , ok := msg .(tuiClipboardResultMsg )
@@ -5323,7 +5326,7 @@ func TestTUIFetchReviewAndCopyEmptyOutput(t *testing.T) {
53235326
53245327 m := newTuiModel (ts .URL )
53255328
5326- cmd := m .fetchReviewAndCopy (123 )
5329+ cmd := m .fetchReviewAndCopy (123 , nil )
53275330 msg := cmd ()
53285331
53295332 result , ok := msg .(tuiClipboardResultMsg )
@@ -5401,7 +5404,7 @@ func TestTUIFetchReviewAndCopyClipboardFailure(t *testing.T) {
54015404 m := newTuiModel (ts .URL )
54025405
54035406 // Fetch succeeds but clipboard write fails
5404- cmd := m .fetchReviewAndCopy (123 )
5407+ cmd := m .fetchReviewAndCopy (123 , nil )
54055408 msg := cmd ()
54065409
54075410 result , ok := msg .(tuiClipboardResultMsg )
@@ -5418,6 +5421,179 @@ func TestTUIFetchReviewAndCopyClipboardFailure(t *testing.T) {
54185421 }
54195422}
54205423
5424+ func TestTUIFetchReviewAndCopyJobInjection (t * testing.T ) {
5425+ // Save original clipboard writer and restore after test
5426+ originalClipboard := clipboardWriter
5427+ mock := & mockClipboard {}
5428+ clipboardWriter = mock
5429+ defer func () { clipboardWriter = originalClipboard }()
5430+
5431+ // Create test server that returns a review WITHOUT Job populated
5432+ ts := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
5433+ review := storage.Review {
5434+ ID : 42 ,
5435+ JobID : 123 ,
5436+ Agent : "test" ,
5437+ Output : "Review content" ,
5438+ // Job is intentionally nil
5439+ }
5440+ json .NewEncoder (w ).Encode (review )
5441+ }))
5442+ defer ts .Close ()
5443+
5444+ m := newTuiModel (ts .URL )
5445+
5446+ // Pass a job parameter - this should be injected when review.Job is nil
5447+ job := & storage.ReviewJob {
5448+ ID : 123 ,
5449+ RepoPath : "/path/to/repo" ,
5450+ GitRef : "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2" , // 40 hex chars
5451+ }
5452+
5453+ cmd := m .fetchReviewAndCopy (123 , job )
5454+ msg := cmd ()
5455+
5456+ result , ok := msg .(tuiClipboardResultMsg )
5457+ if ! ok {
5458+ t .Fatalf ("Expected tuiClipboardResultMsg, got %T" , msg )
5459+ }
5460+
5461+ if result .err != nil {
5462+ t .Errorf ("Expected no error, got %v" , result .err )
5463+ }
5464+
5465+ // Clipboard should contain header with injected job info (truncated SHA)
5466+ expectedContent := "Review #42 /path/to/repo a1b2c3d\n \n Review content"
5467+ if mock .lastText != expectedContent {
5468+ t .Errorf ("Expected clipboard with injected job info, got %q" , mock .lastText )
5469+ }
5470+ }
5471+
5472+ func TestFormatClipboardContent (t * testing.T ) {
5473+ tests := []struct {
5474+ name string
5475+ review * storage.Review
5476+ expected string
5477+ }{
5478+ {
5479+ name : "nil review" ,
5480+ review : nil ,
5481+ expected : "" ,
5482+ },
5483+ {
5484+ name : "empty output" ,
5485+ review : & storage.Review {
5486+ ID : 1 ,
5487+ Output : "" ,
5488+ },
5489+ expected : "" ,
5490+ },
5491+ {
5492+ name : "review with ID only (no job)" ,
5493+ review : & storage.Review {
5494+ ID : 42 ,
5495+ Output : "Content here" ,
5496+ },
5497+ expected : "Review #42\n \n Content here" ,
5498+ },
5499+ {
5500+ name : "review with ID 0 and no job (no header)" ,
5501+ review : & storage.Review {
5502+ ID : 0 ,
5503+ Output : "Content here" ,
5504+ },
5505+ expected : "Content here" ,
5506+ },
5507+ {
5508+ name : "review with job - full SHA truncated" ,
5509+ review : & storage.Review {
5510+ ID : 99 ,
5511+ Output : "Review content" ,
5512+ Job : & storage.ReviewJob {
5513+ ID : 99 ,
5514+ RepoPath : "/Users/test/myrepo" ,
5515+ GitRef : "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2" , // exactly 40 hex chars
5516+ },
5517+ },
5518+ expected : "Review #99 /Users/test/myrepo a1b2c3d\n \n Review content" ,
5519+ },
5520+ {
5521+ name : "long branch name not truncated" ,
5522+ review : & storage.Review {
5523+ ID : 101 ,
5524+ Output : "Review content" ,
5525+ Job : & storage.ReviewJob {
5526+ ID : 101 ,
5527+ RepoPath : "/repo" ,
5528+ GitRef : "feature/very-long-branch-name-that-exceeds-forty-characters" ,
5529+ },
5530+ },
5531+ expected : "Review #101 /repo feature/very-long-branch-name-that-exceeds-forty-characters\n \n Review content" ,
5532+ },
5533+ {
5534+ name : "review with job - range not truncated" ,
5535+ review : & storage.Review {
5536+ ID : 100 ,
5537+ Output : "Review content" ,
5538+ Job : & storage.ReviewJob {
5539+ ID : 100 ,
5540+ RepoPath : "/path/to/repo" ,
5541+ GitRef : "abc1234..def5678" ,
5542+ },
5543+ },
5544+ expected : "Review #100 /path/to/repo abc1234..def5678\n \n Review content" ,
5545+ },
5546+ {
5547+ name : "review ID 0 uses job ID instead" ,
5548+ review : & storage.Review {
5549+ ID : 0 ,
5550+ Output : "Failed job content" ,
5551+ Job : & storage.ReviewJob {
5552+ ID : 555 ,
5553+ RepoPath : "/repo/path" ,
5554+ GitRef : "abcdef1234567890abcdef1234567890abcdef12" ,
5555+ },
5556+ },
5557+ expected : "Review #555 /repo/path abcdef1\n \n Failed job content" ,
5558+ },
5559+ {
5560+ name : "short git ref not truncated" ,
5561+ review : & storage.Review {
5562+ ID : 10 ,
5563+ Output : "Content" ,
5564+ Job : & storage.ReviewJob {
5565+ ID : 10 ,
5566+ RepoPath : "/repo" ,
5567+ GitRef : "abc1234" ,
5568+ },
5569+ },
5570+ expected : "Review #10 /repo abc1234\n \n Content" ,
5571+ },
5572+ {
5573+ name : "uppercase SHA truncated" ,
5574+ review : & storage.Review {
5575+ ID : 102 ,
5576+ Output : "Content" ,
5577+ Job : & storage.ReviewJob {
5578+ ID : 102 ,
5579+ RepoPath : "/repo" ,
5580+ GitRef : "ABCDEF1234567890ABCDEF1234567890ABCDEF12" , // uppercase 40 hex chars
5581+ },
5582+ },
5583+ expected : "Review #102 /repo ABCDEF1\n \n Content" ,
5584+ },
5585+ }
5586+
5587+ for _ , tt := range tests {
5588+ t .Run (tt .name , func (t * testing.T ) {
5589+ got := formatClipboardContent (tt .review )
5590+ if got != tt .expected {
5591+ t .Errorf ("formatClipboardContent() = %q, want %q" , got , tt .expected )
5592+ }
5593+ })
5594+ }
5595+ }
5596+
54215597func TestTUIConfigReloadFlash (t * testing.T ) {
54225598 m := newTuiModel ("http://localhost:7373" )
54235599
0 commit comments