fix QoL and Ai integration
This commit is contained in:
138
tui_test.go
138
tui_test.go
@@ -1013,6 +1013,65 @@ func TestDashboardHardwareCursorMovementChangesZeroWidthFrameMarker(t *testing.T
|
||||
}
|
||||
}
|
||||
|
||||
func TestThreadInputsUseHardwareCursor(t *testing.T) {
|
||||
file, err := os.CreateTemp(t.TempDir(), "cursor-output")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
m := NewApp(&recordingPRService{}, "o", "r", false, 50, time.Second)
|
||||
m.cursorOutput = newTerminalCursorOutput(file)
|
||||
m.screen, m.loading, m.width, m.height = threadScreen, false, 100, 24
|
||||
m.details = PRDetails{
|
||||
PullRequest: PullRequest{RepoWithOwner: "o/r", Number: 1, Title: "Title"},
|
||||
Threads: []ReviewThread{{
|
||||
ID: "thread-1", Path: "main.go", Line: 1,
|
||||
Comments: []ReviewComment{{ID: "comment-1", Author: "alice", Body: "Review"}},
|
||||
}},
|
||||
}
|
||||
|
||||
m.searching, m.searchQuery = true, "main"
|
||||
_ = m.viewThreads()
|
||||
m.cursorOutput.mu.Lock()
|
||||
searchVisible, searchColumn := m.cursorOutput.visible, m.cursorOutput.column
|
||||
m.cursorOutput.mu.Unlock()
|
||||
if !searchVisible || searchColumn <= 0 {
|
||||
t.Fatalf("search cursor visible=%v column=%d", searchVisible, searchColumn)
|
||||
}
|
||||
|
||||
m.searching = false
|
||||
m.aiMode, m.writeThreadID, m.aiInput = aiDiscussion, "thread-1", "Question"
|
||||
m.focus = threadDetailPane
|
||||
m.scroll = m.detailMaxScroll()
|
||||
_ = m.viewThreads()
|
||||
m.cursorOutput.mu.Lock()
|
||||
discussionVisible, beforeSpaceColumn := m.cursorOutput.visible, m.cursorOutput.column
|
||||
m.cursorOutput.mu.Unlock()
|
||||
if !discussionVisible || beforeSpaceColumn <= searchColumn {
|
||||
t.Fatalf(
|
||||
"discussion cursor visible=%v column=%d search column=%d",
|
||||
discussionVisible, beforeSpaceColumn, searchColumn,
|
||||
)
|
||||
}
|
||||
updated, _, handled := m.updateAI(tea.KeyMsg{Type: tea.KeySpace})
|
||||
m = updated.(App)
|
||||
view := m.viewThreads()
|
||||
m.cursorOutput.mu.Lock()
|
||||
afterSpaceColumn := m.cursorOutput.column
|
||||
m.cursorOutput.mu.Unlock()
|
||||
if !handled || m.aiInput != "Question " || afterSpaceColumn != beforeSpaceColumn+1 {
|
||||
t.Fatalf(
|
||||
"handled=%v input=%q cursor before=%d after=%d",
|
||||
handled, m.aiInput, beforeSpaceColumn, afterSpaceColumn,
|
||||
)
|
||||
}
|
||||
if strings.Contains(ansi.Strip(view), "█") ||
|
||||
strings.Contains(ansi.Strip(view), "Question│") {
|
||||
t.Fatalf("thread input still contains a painted cursor: %q", ansi.Strip(view))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDashboardEditorNormalizesMixedLineEndingsWithoutCreatingAnEdit(t *testing.T) {
|
||||
const remoteBody = "first\nsecond\r\nthird\r"
|
||||
m := NewApp(&recordingPRService{}, "o", "r", false, 50, time.Second)
|
||||
@@ -1103,6 +1162,38 @@ func TestReplyComposerRendersInlineWithCurrentThread(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLongThreadCommentRemainsReachableByScrolling(t *testing.T) {
|
||||
m := NewApp(nil, "o", "r", false, 50, time.Second)
|
||||
m.screen, m.loading, m.width, m.height = threadScreen, false, 60, 12
|
||||
m.listHidden, m.focus = true, threadDetailPane
|
||||
body := strings.Repeat("abcdefghij", 40) + "FINALMARKER"
|
||||
m.details = PRDetails{
|
||||
PullRequest: PullRequest{RepoWithOwner: "o/r", Number: 1, Title: "Title"},
|
||||
Threads: []ReviewThread{{
|
||||
ID: "thread", Path: "main.go", Line: 12,
|
||||
Comments: []ReviewComment{{
|
||||
ID: "comment", Author: "reviewer", Body: body,
|
||||
}},
|
||||
}},
|
||||
}
|
||||
|
||||
width, _ := m.detailPaneSize()
|
||||
lines := m.renderedDetailLines(width)
|
||||
for _, line := range lines {
|
||||
available := max(1, width-2-ansi.StringWidth(line.rail))
|
||||
if got := ansi.StringWidth(line.fixed + line.text); got > available {
|
||||
t.Fatalf("detail line width=%d available=%d text=%q", got, available, ansi.Strip(line.text))
|
||||
}
|
||||
}
|
||||
if m.detailMaxScroll() == 0 {
|
||||
t.Fatal("long comment did not produce scrollable detail rows")
|
||||
}
|
||||
m.scroll = m.detailMaxScroll()
|
||||
if view := ansi.Strip(m.viewThreads()); !strings.Contains(view, "FINALMARKER") {
|
||||
t.Fatalf("final comment content is not reachable at maximum scroll:\n%s", view)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveToggleConfirmsAndUsesCurrentThreadState(t *testing.T) {
|
||||
service := &recordingService{}
|
||||
m := NewApp(service, "o", "r", false, 50, time.Second)
|
||||
@@ -1494,12 +1585,29 @@ func TestFuzzyFileSearchSupportsSpaceSeparatedTerms(t *testing.T) {
|
||||
|
||||
m := NewApp(nil, "o", "r", false, 50, 10*time.Second)
|
||||
m.screen, m.searching, m.searchQuery = threadScreen, true, "ng"
|
||||
updated, _ := m.Update(tea.KeyMsg{Type: tea.KeySpace, Runes: []rune{' '}})
|
||||
updated, _ := m.Update(tea.KeyMsg{Type: tea.KeySpace})
|
||||
if got := updated.(App).searchQuery; got != "ng " {
|
||||
t.Fatalf("space key produced search query %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplyAndAIDiscussionAcceptSpaceKeyWithoutRunes(t *testing.T) {
|
||||
m := NewApp(nil, "o", "r", false, 50, 10*time.Second)
|
||||
m.writeMode, m.replyDraft = writeReply, "reply"
|
||||
updated, _ := m.updateWriteInput(tea.KeyMsg{Type: tea.KeySpace})
|
||||
m = updated.(App)
|
||||
if m.replyDraft != "reply " {
|
||||
t.Fatalf("space key produced reply draft %q", m.replyDraft)
|
||||
}
|
||||
|
||||
m.writeMode, m.aiMode, m.aiInput = writeNone, aiDiscussion, "question"
|
||||
updated, _, handled := m.updateAI(tea.KeyMsg{Type: tea.KeySpace})
|
||||
m = updated.(App)
|
||||
if !handled || m.aiInput != "question " {
|
||||
t.Fatalf("handled=%v space key produced AI input %q", handled, m.aiInput)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSearchCanJumpOrCancel(t *testing.T) {
|
||||
m := NewApp(nil, "o", "r", false, 50, 10*time.Second)
|
||||
m.screen = threadScreen
|
||||
@@ -1809,8 +1917,9 @@ func TestDetailsLoadUsesSelectedPRRepository(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPeopleMetadataUsesColoredHandles(t *testing.T) {
|
||||
assignees := handlesText([]string{"alice"})
|
||||
reviewers := reviewersText([]Reviewer{{Login: "bob", State: "APPROVED"}})
|
||||
m := App{}
|
||||
assignees := m.handlesText([]string{"alice"})
|
||||
reviewers := m.reviewersText([]Reviewer{{Login: "bob", State: "APPROVED"}})
|
||||
if ansi.Strip(assignees) != "@alice" || ansi.Strip(reviewers) != "@bob (approved)" {
|
||||
t.Fatalf("people metadata = %q / %q", ansi.Strip(assignees), ansi.Strip(reviewers))
|
||||
}
|
||||
@@ -1820,6 +1929,29 @@ func TestPeopleMetadataUsesColoredHandles(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestViewerLabelCanReplaceGitHubLoginWithYou(t *testing.T) {
|
||||
m := App{
|
||||
viewerLabel: "you",
|
||||
details: PRDetails{
|
||||
ViewerLogin: "pablu",
|
||||
},
|
||||
}
|
||||
if got := ansi.Strip(m.authorText("pablu")); got != "@you" {
|
||||
t.Fatalf("viewer author = %q", got)
|
||||
}
|
||||
if got := ansi.Strip(m.authorText("reviewer")); got != "@reviewer" {
|
||||
t.Fatalf("other author = %q", got)
|
||||
}
|
||||
|
||||
m.viewerLabel = "login"
|
||||
localUser := ReviewComment{
|
||||
Author: "you", Origin: reviewOriginLocalAIUser,
|
||||
}
|
||||
if got := ansi.Strip(m.commentAuthorText(localUser)); got != "@pablu" {
|
||||
t.Fatalf("local AI user author = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDashboardFrameDoesNotChangeWhenBackgroundRefreshStarts(t *testing.T) {
|
||||
m := NewApp(&recordingService{}, "", "", false, 50, time.Minute)
|
||||
m.screen, m.loading, m.width, m.height = dashboardScreen, false, 100, 30
|
||||
|
||||
Reference in New Issue
Block a user