Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 67973b27aa | |||
| 8c390e7505 | |||
| ffacea132c | |||
| f0addf8fad | |||
| 2ea20da5ef |
@@ -377,6 +377,217 @@ func sortTriageQueue(queue []TriageItem) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Issue #117: Tests for GetTriageQueue aggregation ---
|
||||||
|
|
||||||
|
func TestGetTriageQueue_Integration(t *testing.T) {
|
||||||
|
// Mock server that returns issues (some assigned, some not) and PRs.
|
||||||
|
requestCount := 0
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
requestCount++
|
||||||
|
switch {
|
||||||
|
case r.URL.Path == "/api/v1/user/orgs":
|
||||||
|
json.NewEncoder(w).Encode([]Org{{Name: "org1"}})
|
||||||
|
|
||||||
|
case strings.HasPrefix(r.URL.Path, "/api/v1/orgs/org1/repos"):
|
||||||
|
json.NewEncoder(w).Encode([]Repo{
|
||||||
|
{ID: 1, Name: "repo1", FullName: "org1/repo1", Owner: struct {
|
||||||
|
Login string `json:"login"`
|
||||||
|
}{Login: "org1"}},
|
||||||
|
})
|
||||||
|
|
||||||
|
case strings.HasSuffix(r.URL.Path, "/issues") && r.Method == "GET":
|
||||||
|
// Return mix of assigned and unassigned issues.
|
||||||
|
issues := []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"id": 1, "number": 1, "title": "Unassigned bug",
|
||||||
|
"state": "open", "assignee": nil, "assignees": []interface{}{},
|
||||||
|
"labels": []map[string]interface{}{{"id": 1, "name": "P1", "color": "ff0000"}},
|
||||||
|
"html_url": "http://example.com/org1/repo1/issues/1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2, "number": 2, "title": "Assigned issue",
|
||||||
|
"state": "open",
|
||||||
|
"assignee": map[string]string{"login": "dev1", "avatar_url": ""},
|
||||||
|
"assignees": []map[string]string{{"login": "dev1", "avatar_url": ""}},
|
||||||
|
"labels": []interface{}{},
|
||||||
|
"html_url": "http://example.com/org1/repo1/issues/2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3, "number": 3, "title": "Unassigned low priority",
|
||||||
|
"state": "open", "assignee": nil, "assignees": []interface{}{},
|
||||||
|
"labels": []map[string]interface{}{{"id": 2, "name": "P3", "color": "00ff00"}},
|
||||||
|
"html_url": "http://example.com/org1/repo1/issues/3",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(issues)
|
||||||
|
|
||||||
|
case strings.HasSuffix(r.URL.Path, "/pulls") && r.Method == "GET":
|
||||||
|
prs := []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"id": 10, "number": 10, "title": "Open PR needs review",
|
||||||
|
"state": "open", "body": "please review",
|
||||||
|
"labels": []map[string]interface{}{{"id": 3, "name": "P2", "color": "ffff00"}},
|
||||||
|
"html_url": "http://example.com/org1/repo1/pulls/10",
|
||||||
|
"head": map[string]string{"label": "feature", "ref": "feature"},
|
||||||
|
"base": map[string]string{"label": "master", "ref": "master"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(prs)
|
||||||
|
|
||||||
|
case strings.HasSuffix(r.URL.Path, "/reviews"):
|
||||||
|
json.NewEncoder(w).Encode([]interface{}{})
|
||||||
|
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
fmt.Fprintf(w, "unexpected request: %s %s", r.Method, r.URL.Path)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
c := NewClient(server.URL)
|
||||||
|
queue, err := c.GetTriageQueue(context.Background(), "test-token", []string{"org1"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetTriageQueue: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should include: 2 unassigned issues + 1 PR = 3 items.
|
||||||
|
// Assigned issue (#2) should be excluded.
|
||||||
|
if len(queue) != 3 {
|
||||||
|
t.Fatalf("expected 3 triage items, got %d", len(queue))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify sorting: P1 > P2 > P3.
|
||||||
|
if queue[0].Title != "Unassigned bug" {
|
||||||
|
t.Errorf("queue[0] should be P1 'Unassigned bug', got %q", queue[0].Title)
|
||||||
|
}
|
||||||
|
if queue[1].Title != "Open PR needs review" {
|
||||||
|
t.Errorf("queue[1] should be P2 'Open PR needs review', got %q", queue[1].Title)
|
||||||
|
}
|
||||||
|
if queue[2].Title != "Unassigned low priority" {
|
||||||
|
t.Errorf("queue[2] should be P3 'Unassigned low priority', got %q", queue[2].Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify types.
|
||||||
|
if queue[0].Type != "issue" {
|
||||||
|
t.Errorf("queue[0].Type = %q, want 'issue'", queue[0].Type)
|
||||||
|
}
|
||||||
|
if queue[1].Type != "pull" {
|
||||||
|
t.Errorf("queue[1].Type = %q, want 'pull'", queue[1].Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTriageQueue_EmptyOrgs(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch {
|
||||||
|
case r.URL.Path == "/api/v1/user/orgs":
|
||||||
|
json.NewEncoder(w).Encode([]Org{})
|
||||||
|
default:
|
||||||
|
json.NewEncoder(w).Encode([]interface{}{})
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
c := NewClient(server.URL)
|
||||||
|
queue, err := c.GetTriageQueue(context.Background(), "test-token", []string{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetTriageQueue with empty orgs: %v", err)
|
||||||
|
}
|
||||||
|
if len(queue) != 0 {
|
||||||
|
t.Errorf("expected empty queue for empty orgs, got %d items", len(queue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTriageQueue_AllAssigned(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch {
|
||||||
|
case r.URL.Path == "/api/v1/user/orgs":
|
||||||
|
json.NewEncoder(w).Encode([]Org{{Name: "org1"}})
|
||||||
|
case strings.HasPrefix(r.URL.Path, "/api/v1/orgs/org1/repos"):
|
||||||
|
json.NewEncoder(w).Encode([]Repo{
|
||||||
|
{ID: 1, Name: "repo1", FullName: "org1/repo1", Owner: struct {
|
||||||
|
Login string `json:"login"`
|
||||||
|
}{Login: "org1"}},
|
||||||
|
})
|
||||||
|
case strings.HasSuffix(r.URL.Path, "/issues"):
|
||||||
|
// All issues are assigned.
|
||||||
|
json.NewEncoder(w).Encode([]map[string]interface{}{
|
||||||
|
{
|
||||||
|
"id": 1, "number": 1, "title": "Assigned issue",
|
||||||
|
"state": "open",
|
||||||
|
"assignee": map[string]string{"login": "dev1"},
|
||||||
|
"assignees": []map[string]string{{"login": "dev1"}},
|
||||||
|
"labels": []interface{}{},
|
||||||
|
"html_url": "http://example.com/org1/repo1/issues/1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
case strings.HasSuffix(r.URL.Path, "/pulls"):
|
||||||
|
json.NewEncoder(w).Encode([]interface{}{}) // No PRs.
|
||||||
|
case strings.HasSuffix(r.URL.Path, "/reviews"):
|
||||||
|
json.NewEncoder(w).Encode([]interface{}{})
|
||||||
|
default:
|
||||||
|
json.NewEncoder(w).Encode([]interface{}{})
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
c := NewClient(server.URL)
|
||||||
|
queue, err := c.GetTriageQueue(context.Background(), "test-token", []string{"org1"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetTriageQueue: %v", err)
|
||||||
|
}
|
||||||
|
// Only PRs should appear (none here), all issues are assigned.
|
||||||
|
if len(queue) != 0 {
|
||||||
|
t.Errorf("expected 0 items (all assigned), got %d", len(queue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTriageQueue_LabelExtraction(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch {
|
||||||
|
case r.URL.Path == "/api/v1/user/orgs":
|
||||||
|
json.NewEncoder(w).Encode([]Org{{Name: "org1"}})
|
||||||
|
case strings.HasPrefix(r.URL.Path, "/api/v1/orgs/org1/repos"):
|
||||||
|
json.NewEncoder(w).Encode([]Repo{
|
||||||
|
{ID: 1, Name: "repo1", FullName: "org1/repo1", Owner: struct {
|
||||||
|
Login string `json:"login"`
|
||||||
|
}{Login: "org1"}},
|
||||||
|
})
|
||||||
|
case strings.HasSuffix(r.URL.Path, "/issues"):
|
||||||
|
json.NewEncoder(w).Encode([]map[string]interface{}{
|
||||||
|
{
|
||||||
|
"id": 1, "number": 1, "title": "Multi-label issue",
|
||||||
|
"state": "open", "assignee": nil, "assignees": []interface{}{},
|
||||||
|
"labels": []map[string]interface{}{
|
||||||
|
{"id": 1, "name": "bug", "color": "d73a4a"},
|
||||||
|
{"id": 2, "name": "P1", "color": "ff0000"},
|
||||||
|
{"id": 3, "name": "help wanted", "color": "0e8a16"},
|
||||||
|
},
|
||||||
|
"html_url": "http://example.com/org1/repo1/issues/1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
case strings.HasSuffix(r.URL.Path, "/pulls"):
|
||||||
|
json.NewEncoder(w).Encode([]interface{}{})
|
||||||
|
case strings.HasSuffix(r.URL.Path, "/reviews"):
|
||||||
|
json.NewEncoder(w).Encode([]interface{}{})
|
||||||
|
default:
|
||||||
|
json.NewEncoder(w).Encode([]interface{}{})
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
c := NewClient(server.URL)
|
||||||
|
queue, err := c.GetTriageQueue(context.Background(), "test-token", []string{"org1"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetTriageQueue: %v", err)
|
||||||
|
}
|
||||||
|
if len(queue) != 1 {
|
||||||
|
t.Fatalf("expected 1 item, got %d", len(queue))
|
||||||
|
}
|
||||||
|
if len(queue[0].Labels) != 3 {
|
||||||
|
t.Errorf("expected 3 labels, got %d: %v", len(queue[0].Labels), queue[0].Labels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- Issue #122: Tests for ListOrgsAndRepos and CreateIssue ---
|
// --- Issue #122: Tests for ListOrgsAndRepos and CreateIssue ---
|
||||||
|
|
||||||
func TestListOrgsAndRepos(t *testing.T) {
|
func TestListOrgsAndRepos(t *testing.T) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+53
-2
@@ -1,4 +1,12 @@
|
|||||||
/* Gitea Mobile — Mobile-first CSS (~5KB target) */
|
/* Gitea Mobile — Mobile-first CSS
|
||||||
|
* Dark-mode-first: dark colors are the :root defaults.
|
||||||
|
* Light mode is applied via @media (prefers-color-scheme: light).
|
||||||
|
*
|
||||||
|
* Size note: The original ~5KB target was based on the initial Phase 1 scope.
|
||||||
|
* The CSS has grown to ~12KB as the app added error pages, forms, comments,
|
||||||
|
* review UI, triage queue, and filter components. All rules are in active use.
|
||||||
|
* Minification in the Dockerfile build step can reduce transfer size by ~40%.
|
||||||
|
*/
|
||||||
|
|
||||||
/* Reset */
|
/* Reset */
|
||||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
@@ -533,7 +541,7 @@ a:active {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark mode is default; light mode override if needed */
|
/* Dark mode is default; light mode override for prefers-color-scheme: light */
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
:root {
|
:root {
|
||||||
--bg-primary: #ffffff;
|
--bg-primary: #ffffff;
|
||||||
@@ -543,6 +551,49 @@ a:active {
|
|||||||
--text-primary: #1f2328;
|
--text-primary: #1f2328;
|
||||||
--text-secondary: #656d76;
|
--text-secondary: #656d76;
|
||||||
--text-link: #0969da;
|
--text-link: #0969da;
|
||||||
|
--accent-green: #1a7f37;
|
||||||
|
--accent-red: #cf222e;
|
||||||
|
--accent-yellow: #9a6700;
|
||||||
|
--accent-blue: #0969da;
|
||||||
|
--accent-purple: #8250df;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.success {
|
||||||
|
background: #dafbe1;
|
||||||
|
border-color: #1a7f37;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.error {
|
||||||
|
background: #ffebe9;
|
||||||
|
border-color: #cf222e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.info {
|
||||||
|
background: #ddf4ff;
|
||||||
|
border-color: #0969da;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #1a7f37;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:active {
|
||||||
|
background: #116329;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: #ffebe9;
|
||||||
|
border-color: #cf222e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-issue {
|
||||||
|
background: rgba(9, 105, 218, 0.1);
|
||||||
|
border-color: rgba(9, 105, 218, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-pull {
|
||||||
|
background: rgba(26, 127, 55, 0.1);
|
||||||
|
border-color: rgba(26, 127, 55, 0.3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user