Files
gitea-tea/internal/terminal/review_form_test.go
T
bircni c5cf6b9ed9 feat(pr): review with -a/-r/-c, a review create command and the current branch's PR
tea pr review takes gh's review flags: -a/--approve, -r/--request-changes
and -c/--comment, with -b/--body, -F/--body-file and -e/--editor, and
--pending for a review to submit later. --event is hidden but still
accepted, case-insensitively, as approve, approved, request-changes,
request_changes, comment or pending. tea pr review create is the same
command under the name people look for.

The pull request argument is optional and accepts #N, URLs and branch
names; without one, the pull request of the current branch is reviewed.
Without a review type, tea opens the interactive review on a terminal
and otherwise fails with "choose a review type" instead of posting a
comment. Review types given as a subcommand ("tea pr review approve 13")
point at the flag, and approving or requesting changes on one's own pull
request fails before any editor opens.

The interactive review asks for the type first, hiding Approve and
Request changes for the author, then for an inline body that ctrl+e
opens in the configured editor, then offers to annotate the diff, and
confirms with a default of yes. A declined submission keeps the body as
a draft.

Successful reviews report "✓ Approved pull request alice/x#13 (title)"
on a terminal and print the review URL. review submit finds the
caller's pending review when no ID is given, review list and comments
are tables with headers, and dismiss, undismiss, delete, request and
cancel-request say what they did. Every review command has examples and
completes pull request and review IDs.

Signed-off-by: bircni <bircni@icloud.com>
2026-10-11 23:54:45 +02:00

78 lines
3.1 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package terminal
import (
"context"
"errors"
"strings"
"testing"
"testing/iotest"
"github.com/stretchr/testify/require"
)
// linePrompter is accessiblePrompter reading its input a byte at a time, so that every field of
// consecutive forms gets its own line.
func linePrompter(t *testing.T, input string) (*FormPrompter, *strings.Builder) {
t.Helper()
t.Setenv("TERM", "dumb")
p, _ := testPrompter(iotest.OneByteReader(strings.NewReader(input)))
rendered := &strings.Builder{}
p.Streams.ErrOut = rendered
return p, rendered
}
func TestCreateReviewAbortsWhenDiffReviewFails(t *testing.T) {
failure := errors.New("editor failed")
p, _ := linePrompter(t, "1\nLooks good\ny\n")
_, err := p.CreateReview(context.Background(), ReviewFormChoices{
ReviewDiff: func(context.Context) ([]ReviewCommentDraft, error) { return nil, failure },
})
require.ErrorIs(t, err, failure)
}
func TestReviewFormAsksTypeThenBody(t *testing.T) {
for _, tc := range []struct {
name, input string
choices ReviewFormChoices
want ReviewFormValues
title, err string
hidden []string
}{
{name: "approve without body", input: "1\n\n", want: ReviewFormValues{Event: "APPROVED"}, title: "Body (markdown, optional):"},
{name: "request changes", input: "2\nPlease add tests\n", want: ReviewFormValues{Event: "REQUEST_CHANGES", Body: "Please add tests"}, title: "Body (markdown, required):"},
{name: "comment needs a body", input: "3\n\n", err: errEmptyComment.Error(), title: "Body (markdown, required):"},
{name: "author can only comment", input: "1\nSelf note\n", choices: ReviewFormChoices{Author: true}, want: ReviewFormValues{Event: "COMMENT", Body: "Self note"}, hidden: []string{"Approve", "Request changes"}},
{name: "editor shortcut", input: "1\n\n", choices: ReviewFormChoices{Editor: "vi"}, want: ReviewFormValues{Event: "APPROVED"}, title: "Body (markdown, optional, ctrl+e opens your editor):"},
{name: "header", input: "1\n\n", choices: ReviewFormChoices{Header: "Reviewing #1 \"Add\x1b[31m one\" (feat/one → main) in alice/x"}, want: ReviewFormValues{Event: "APPROVED"}, title: "Reviewing #1 \"Add[31m one\" (feat/one → main) in alice/x"},
} {
t.Run(tc.name, func(t *testing.T) {
p, rendered := linePrompter(t, tc.input)
values, err := p.CreateReview(context.Background(), tc.choices)
if tc.err != "" {
require.EqualError(t, err, tc.err)
} else {
require.NoError(t, err)
require.Equal(t, tc.want, values)
}
require.Contains(t, rendered.String(), tc.title)
for _, hidden := range tc.hidden {
require.NotContains(t, rendered.String(), hidden)
}
})
}
}
func TestReviewFormCommentMayBeInlineOnly(t *testing.T) {
p, _ := linePrompter(t, "3\n\ny\n")
values, err := p.CreateReview(context.Background(), ReviewFormChoices{
ReviewDiff: func(context.Context) ([]ReviewCommentDraft, error) {
return []ReviewCommentDraft{{Path: "one.txt", Body: "nit", NewLine: 1}}, nil
},
})
require.NoError(t, err)
require.Equal(t, ReviewFormValues{Event: "COMMENT", Comments: []ReviewCommentDraft{{Path: "one.txt", Body: "nit", NewLine: 1}}}, values)
}