0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-02-21 13:38:39 +01:00
gitea/tests/integration/compare_test.go
silverwind e643492464
Batch blob_excerpt requests for "Expand All" into a single fetch
When expanding all collapsed sections in a diff, the frontend now
collects per-gap parameters from all expander buttons, joins them with
commas, and makes a single GET request. The backend detects batch mode
(comma in last_left), renders each section to HTML, and returns a JSON
string array. Single-section expands are unchanged.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 06:22:31 +01:00

295 lines
10 KiB
Go

// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/http"
"net/url"
"strings"
"testing"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/test"
repo_service "code.gitea.io/gitea/services/repository"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCompareTag(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")
req := NewRequest(t, "GET", "/user2/repo1/compare/v1.1...master")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
selection := htmlDoc.doc.Find(".ui.dropdown.select-branch")
// A dropdown for both base and head.
assert.Lenf(t, selection.Nodes, 2, "The template has changed")
req = NewRequest(t, "GET", "/user2/repo1/compare/invalid").SetHeader("Accept", "text/html")
resp = session.MakeRequest(t, req, http.StatusNotFound)
assert.True(t, test.IsNormalPageCompleted(resp.Body.String()), "expect 404 page not 500")
}
// Compare with inferred default branch (master)
func TestCompareDefault(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")
req := NewRequest(t, "GET", "/user2/repo1/compare/v1.1")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
selection := htmlDoc.doc.Find(".ui.dropdown.select-branch")
assert.Lenf(t, selection.Nodes, 2, "The template has changed")
}
// Ensure the comparison matches what we expect
func inspectCompare(t *testing.T, htmlDoc *HTMLDoc, diffCount int, diffChanges []string) {
selection := htmlDoc.doc.Find("#diff-file-boxes").Children()
assert.Lenf(t, selection.Nodes, diffCount, "Expected %v diffed files, found: %v", diffCount, len(selection.Nodes))
for _, diffChange := range diffChanges {
selection = htmlDoc.doc.Find(fmt.Sprintf("[data-new-filename=\"%s\"]", diffChange))
assert.Lenf(t, selection.Nodes, 1, "Expected 1 match for [data-new-filename=\"%s\"], found: %v", diffChange, len(selection.Nodes))
}
}
// Git commit graph for repo20
// * 8babce9 (origin/remove-files-b) Add a dummy file
// * b67e43a Delete test.csv and link_hi
// | * cfe3b3c (origin/remove-files-a) Delete test.csv and link_hi
// |/
// * c8e31bc (origin/add-csv) Add test csv file
// * 808038d (HEAD -> master, origin/master, origin/HEAD) Added test links
func TestCompareBranches(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")
// Indirect compare remove-files-b (head) with add-csv (base) branch
//
// 'link_hi' and 'test.csv' are deleted, 'test.txt' is added
req := NewRequest(t, "GET", "/user2/repo20/compare/add-csv...remove-files-b")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
diffCount := 3
diffChanges := []string{"link_hi", "test.csv", "test.txt"}
inspectCompare(t, htmlDoc, diffCount, diffChanges)
// Indirect compare remove-files-b (head) with remove-files-a (base) branch
//
// 'link_hi' and 'test.csv' are deleted, 'test.txt' is added
req = NewRequest(t, "GET", "/user2/repo20/compare/remove-files-a...remove-files-b")
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body)
diffCount = 3
diffChanges = []string{"link_hi", "test.csv", "test.txt"}
inspectCompare(t, htmlDoc, diffCount, diffChanges)
// Indirect compare remove-files-a (head) with remove-files-b (base) branch
//
// 'link_hi' and 'test.csv' are deleted
req = NewRequest(t, "GET", "/user2/repo20/compare/remove-files-b...remove-files-a")
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body)
diffCount = 2
diffChanges = []string{"link_hi", "test.csv"}
inspectCompare(t, htmlDoc, diffCount, diffChanges)
// Direct compare remove-files-b (head) with remove-files-a (base) branch
//
// 'test.txt' is deleted
req = NewRequest(t, "GET", "/user2/repo20/compare/remove-files-b..remove-files-a")
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body)
diffCount = 1
diffChanges = []string{"test.txt"}
inspectCompare(t, htmlDoc, diffCount, diffChanges)
}
func TestCompareCodeExpand(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
repo, err := repo_service.CreateRepositoryDirectly(t.Context(), user1, user1, repo_service.CreateRepoOptions{
Name: "test_blob_excerpt",
Readme: "Default",
AutoInit: true,
DefaultBranch: "main",
}, true)
assert.NoError(t, err)
session := loginUser(t, user1.Name)
testEditFile(t, session, user1.Name, repo.Name, "main", "README.md", strings.Repeat("a\n", 30))
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
session = loginUser(t, user2.Name)
testRepoFork(t, session, user1.Name, repo.Name, user2.Name, "test_blob_excerpt-fork", "")
testCreateBranch(t, session, user2.Name, "test_blob_excerpt-fork", "branch/main", "forked-branch", http.StatusSeeOther)
testEditFile(t, session, user2.Name, "test_blob_excerpt-fork", "forked-branch", "README.md", strings.Repeat("a\n", 15)+"CHANGED\n"+strings.Repeat("a\n", 15))
req := NewRequest(t, "GET", "/user1/test_blob_excerpt/compare/main...user2/test_blob_excerpt-fork:forked-branch")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
els := htmlDoc.Find(`button.code-expander-button[data-url]`)
// all the links in the comparison should be to the forked repo&branch
assert.NotZero(t, els.Length())
for i := 0; i < els.Length(); i++ {
link := els.Eq(i).AttrOr("data-url", "")
assert.True(t, strings.HasPrefix(link, "/user2/test_blob_excerpt-fork/blob_excerpt/"))
}
})
}
func TestBlobExcerptSingleAndBatch(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
repo, err := repo_service.CreateRepositoryDirectly(t.Context(), user1, user1, repo_service.CreateRepoOptions{
Name: "test_blob_excerpt_batch",
Readme: "Default",
AutoInit: true,
DefaultBranch: "main",
}, true)
require.NoError(t, err)
session := loginUser(t, user1.Name)
// Create a file with 50 lines so the diff has multiple collapsed sections
lines := make([]string, 50)
for i := range lines {
lines[i] = fmt.Sprintf("line %d", i+1)
}
testEditFile(t, session, user1.Name, repo.Name, "main", "README.md", strings.Join(lines, "\n")+"\n")
// Create a branch and change a line in the middle to produce two expander gaps
testEditFileToNewBranch(t, session, user1.Name, repo.Name, "main", "excerpt-branch", "README.md",
func() string {
modified := make([]string, 50)
copy(modified, lines)
modified[24] = "CHANGED line 25"
return strings.Join(modified, "\n") + "\n"
}(),
)
// Load the compare page
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/compare/main...excerpt-branch", user1.Name, repo.Name))
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
els := htmlDoc.Find(`button.code-expander-button[data-url]`)
// We need at least 2 expander buttons to test batch mode
require.GreaterOrEqual(t, els.Length(), 2, "expected at least 2 expander buttons")
// Deduplicate by anchor param to get one URL per collapsed section
// (updown rows have two buttons with the same anchor but different directions)
seen := map[string]bool{}
var expanderURLs []string
for i := range els.Length() {
link := els.Eq(i).AttrOr("data-url", "")
parsed, err := url.Parse(link)
require.NoError(t, err)
anchor := parsed.Query().Get("anchor")
if !seen[anchor] {
seen[anchor] = true
expanderURLs = append(expanderURLs, link)
}
}
require.GreaterOrEqual(t, len(expanderURLs), 2, "expected at least 2 unique expander sections")
t.Run("SingleFetch", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// Rewrite direction to "full" as the frontend does for expand-all
singleURL := strings.Replace(expanderURLs[0], "direction=down", "direction=full", 1)
singleURL = strings.Replace(singleURL, "direction=up", "direction=full", 1)
req := NewRequest(t, "GET", singleURL)
resp := session.MakeRequest(t, req, http.StatusOK)
body := resp.Body.String()
// Single mode returns HTML directly, should contain diff table rows
assert.Contains(t, body, `class="lines-`)
assert.NotContains(t, body, `[`) // should not be JSON
})
t.Run("BatchFetch", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// Parse per-gap params from each expander URL and join with commas
paramKeys := []string{"last_left", "last_right", "left", "right", "left_hunk_size", "right_hunk_size"}
batchValues := make(map[string][]string)
var basePath string
var sharedParams url.Values
for i, expanderURL := range expanderURLs {
parsed, err := url.Parse(expanderURL)
require.NoError(t, err)
if i == 0 {
basePath = parsed.Path
sharedParams = parsed.Query()
}
q := parsed.Query()
for _, key := range paramKeys {
batchValues[key] = append(batchValues[key], q.Get(key))
}
}
// Build batch URL
batchParams := url.Values{}
for _, key := range paramKeys {
batchParams.Set(key, strings.Join(batchValues[key], ","))
}
for _, key := range []string{"path", "filelang", "style"} {
if v := sharedParams.Get(key); v != "" {
batchParams.Set(key, v)
}
}
batchParams.Set("direction", "full")
batchURL := basePath + "?" + batchParams.Encode()
req := NewRequest(t, "GET", batchURL)
resp := session.MakeRequest(t, req, http.StatusOK)
// Batch mode returns a JSON array of HTML strings
var htmlArray []string
err := json.Unmarshal(resp.Body.Bytes(), &htmlArray)
require.NoError(t, err, "response should be valid JSON string array")
assert.Len(t, htmlArray, len(expanderURLs))
for i, html := range htmlArray {
assert.Contains(t, html, `class="lines-`, "batch result %d should contain diff HTML", i)
}
})
t.Run("BatchFetchMismatchedParams", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
// Build a batch URL with mismatched param lengths — should return 400
parsed, err := url.Parse(expanderURLs[0])
require.NoError(t, err)
q := parsed.Query()
q.Set("last_left", q.Get("last_left")+",0") // 2 values
// other params remain with 1 value
badURL := parsed.Path + "?" + q.Encode()
req := NewRequest(t, "GET", badURL)
session.MakeRequest(t, req, http.StatusBadRequest)
})
})
}