mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
issue list renders through the shared table: a context line, ID TITLE LABELS [STATE] [KIND] AUTHOR UPDATED with relative times on a terminal, the ID colored by state and linked, and a stable tab-separated column set when piped; empty results say so on stderr. It gains -s -L -l -a -A -m -S -w, @me, comma-separated label and milestone filters, and --web. issue view, edit, close and reopen accept #N, OWNER/REPO#N and issue URLs. Mutations report "✓ Created/Edited/Closed issue alice/x#5 (title)" on a terminal; issue create prints the new issue's URL on stdout (also as issue new), and close and reopen skip issues already in that state with a ! line and take -c/--comment. issue create and edit gain the gh short flags and singular --add-label, --remove-label, --add-assignee and --remove-assignee (the plural names still work), and create gains --web. The interactive create form asks for the title and body, then "What's next?" (submit, add metadata or cancel), without a confirmation after it; the edit form names the issue it edits. Every issue command has help text with examples and completes its flags and issue numbers. Signed-off-by: bircni <bircni@icloud.com>
91 lines
3.0 KiB
Go
91 lines
3.0 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package terminal
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestCollectIssuePropertiesUsesEditBodyDraft(t *testing.T) {
|
|
var initial string
|
|
p, _ := testPrompter(bytes.NewBufferString("Drafted Title\r"))
|
|
values, err := p.collectIssueProperties(context.Background(), "", "template body", IssueFormChoices{
|
|
EditBody: func(_ context.Context, body string) (Draft, error) {
|
|
initial = body
|
|
return Draft{Path: filepath.Join(t.TempDir(), "draft.md"), Body: "edited body"}, nil
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, "template body", initial)
|
|
require.Equal(t, "Drafted Title", values.Title)
|
|
require.Equal(t, "edited body", values.Body)
|
|
require.NotEmpty(t, values.Draft.Path)
|
|
}
|
|
|
|
// lineReader returns one line per read, so that each accessible prompt reads only its answer.
|
|
type lineReader struct{ text string }
|
|
|
|
func (r *lineReader) Read(p []byte) (int, error) {
|
|
if r.text == "" {
|
|
return 0, io.EOF
|
|
}
|
|
line, _, _ := strings.Cut(r.text, "\n")
|
|
n := copy(p, line+"\n")
|
|
r.text = r.text[min(n, len(r.text)):]
|
|
return n, nil
|
|
}
|
|
|
|
func TestIssueFormAsksWhatsNextBeforeMetadata(t *testing.T) {
|
|
choices := IssueFormChoices{AskNext: true, CanPush: true, Assignees: []string{"alice"}, EditBody: noEdit}
|
|
for _, test := range []struct {
|
|
name, input string
|
|
canceled bool
|
|
}{
|
|
{"submit", "Title\n1\n", false},
|
|
{"cancel", "Title\n3\n", true},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Setenv("TERM", "dumb")
|
|
p, rendered := testPrompter(&lineReader{text: test.input})
|
|
values, err := p.collectIssueProperties(context.Background(), "", "", choices)
|
|
require.Contains(t, rendered.String(), "What's next?")
|
|
require.Contains(t, rendered.String(), "Add metadata")
|
|
require.NotContains(t, rendered.String(), "Assignees:")
|
|
if test.canceled {
|
|
require.ErrorIs(t, err, ErrCanceled)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
require.Equal(t, "Title", values.Title)
|
|
require.Empty(t, values.Assignees)
|
|
})
|
|
}
|
|
t.Setenv("TERM", "dumb")
|
|
p, rendered := testPrompter(&lineReader{text: "Title\n2\n1\n0\n2028-01-15\n"})
|
|
values, err := p.collectIssueProperties(context.Background(), "", "", choices)
|
|
require.NoError(t, err)
|
|
require.Contains(t, rendered.String(), "Assignees:")
|
|
require.Equal(t, []string{"alice"}, values.Assignees)
|
|
require.Equal(t, "2028-01-15", values.Deadline.Format(time.DateOnly))
|
|
p, rendered = accessiblePrompter(t, "Title\n")
|
|
_, err = p.collectIssueProperties(context.Background(), "", "", IssueFormChoices{EditBody: noEdit})
|
|
require.NoError(t, err)
|
|
require.NotContains(t, rendered.String(), "What's next?")
|
|
}
|
|
|
|
func TestIssueFormMarksTitleRequired(t *testing.T) {
|
|
p, rendered := accessiblePrompter(t, "Title\n")
|
|
_, err := p.collectIssueProperties(context.Background(), "", "", IssueFormChoices{EditBody: noEdit})
|
|
require.NoError(t, err)
|
|
require.Contains(t, rendered.String(), "Title (required):")
|
|
}
|