fix: preserve object format dropdown options on /repo/create error (#4360)

To reproduce:
- make the repo creation form return with an error, like a duplicate name
- click on the Object format dropdown
- the options are missing as the listbox is empty

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/4360
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Solomon Victorino <git@solomonvictorino.com>
Co-committed-by: Solomon Victorino <git@solomonvictorino.com>
This commit is contained in:
Solomon Victorino 2024-07-16 14:42:35 +00:00 committed by Earl Warren
parent 8e56f61d0f
commit df22f8da5f
4 changed files with 89 additions and 21 deletions

View file

@ -231,6 +231,8 @@ func CreatePost(ctx *context.Context) {
ctx.Data["CanCreateRepo"] = ctx.Doer.CanCreateRepo() ctx.Data["CanCreateRepo"] = ctx.Doer.CanCreateRepo()
ctx.Data["MaxCreationLimit"] = ctx.Doer.MaxCreationLimit() ctx.Data["MaxCreationLimit"] = ctx.Doer.MaxCreationLimit()
ctx.Data["SupportedObjectFormats"] = git.SupportedObjectFormats
ctx.Data["DefaultObjectFormat"] = git.Sha1ObjectFormat
ctxUser := checkContextUser(ctx, form.UID) ctxUser := checkContextUser(ctx, form.UID)
if ctx.Written() { if ctx.Written() {

View file

@ -69,7 +69,7 @@
<div class="inline field"> <div class="inline field">
<label>{{ctx.Locale.Tr "repo.template"}}</label> <label>{{ctx.Locale.Tr "repo.template"}}</label>
<div id="repo_template_search" class="ui search selection dropdown"> <div id="repo_template_search" class="ui search selection dropdown">
<input type="hidden" id="repo_template" name="repo_template" value="{{.repo_template}}"> <input type="hidden" id="repo_template" name="repo_template" value="{{if ne .repo_template 0}}{{.repo_template}}{{end}}">
<div class="default text">{{.repo_template_name}}</div> <div class="default text">{{.repo_template_name}}</div>
<div class="menu"> <div class="menu">
</div> </div>

View file

@ -5,6 +5,7 @@ package integration
import ( import (
"bytes" "bytes"
"fmt"
"testing" "testing"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
@ -36,6 +37,37 @@ func (doc *HTMLDoc) GetInputValueByName(name string) string {
return text return text
} }
func (doc *HTMLDoc) AssertDropdown(t testing.TB, name string) *goquery.Selection {
t.Helper()
dropdownGroup := doc.Find(fmt.Sprintf(".dropdown:has(input[name='%s'])", name))
assert.Equal(t, dropdownGroup.Length(), 1, fmt.Sprintf("%s dropdown does not exist", name))
return dropdownGroup
}
// Assert that a dropdown has at least one non-empty option
func (doc *HTMLDoc) AssertDropdownHasOptions(t testing.TB, dropdownName string) {
t.Helper()
options := doc.AssertDropdown(t, dropdownName).Find(".menu [data-value]:not([data-value=''])")
assert.Greater(t, options.Length(), 0, fmt.Sprintf("%s dropdown has no options", dropdownName))
}
func (doc *HTMLDoc) AssertDropdownHasSelectedOption(t testing.TB, dropdownName, expectedValue string) {
t.Helper()
dropdownGroup := doc.AssertDropdown(t, dropdownName)
selectedValue, _ := dropdownGroup.Find(fmt.Sprintf("input[name='%s']", dropdownName)).Attr("value")
assert.Equal(t, expectedValue, selectedValue, fmt.Sprintf("%s dropdown doesn't have expected value selected", dropdownName))
dropdownValues := dropdownGroup.Find(".menu [data-value]").Map(func(i int, s *goquery.Selection) string {
value, _ := s.Attr("data-value")
return value
})
assert.Contains(t, dropdownValues, expectedValue, fmt.Sprintf("%s dropdown doesn't have an option with expected value", dropdownName))
}
// Find gets the descendants of each element in the current set of // Find gets the descendants of each element in the current set of
// matched elements, filtered by a selector. It returns a new Selection // matched elements, filtered by a selector. It returns a new Selection
// object containing these matched elements. // object containing these matched elements.

View file

@ -6,7 +6,7 @@ package integration
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest" "strconv"
"strings" "strings"
"testing" "testing"
@ -18,11 +18,23 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func testRepoGenerate(t *testing.T, session *TestSession, templateID, templateOwnerName, templateRepoName, generateOwnerName, generateRepoName string) *httptest.ResponseRecorder { func assertRepoCreateForm(t *testing.T, htmlDoc *HTMLDoc, owner *user_model.User, templateID string) {
generateOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: generateOwnerName}) _, exists := htmlDoc.doc.Find("form.ui.form[action^='/repo/create']").Attr("action")
assert.True(t, exists, "Expected the repo creation form")
htmlDoc.AssertDropdownHasSelectedOption(t, "uid", strconv.FormatInt(owner.ID, 10))
// the template menu is loaded client-side, so don't assert the option exists
assert.Equal(t, templateID, htmlDoc.GetInputValueByName("repo_template"), "Unexpected repo_template selection")
for _, name := range []string{"issue_labels", "gitignores", "license", "readme", "object_format_name"} {
htmlDoc.AssertDropdownHasOptions(t, name)
}
}
func testRepoGenerate(t *testing.T, session *TestSession, templateID, templateOwnerName, templateRepoName string, user, generateOwner *user_model.User, generateRepoName string) {
// Step0: check the existence of the generated repo // Step0: check the existence of the generated repo
req := NewRequestf(t, "GET", "/%s/%s", generateOwnerName, generateRepoName) req := NewRequestf(t, "GET", "/%s/%s", generateOwner.Name, generateRepoName)
session.MakeRequest(t, req, http.StatusNotFound) session.MakeRequest(t, req, http.StatusNotFound)
// Step1: go to the main page of template repo // Step1: go to the main page of template repo
@ -36,12 +48,9 @@ func testRepoGenerate(t *testing.T, session *TestSession, templateID, templateOw
req = NewRequest(t, "GET", link) req = NewRequest(t, "GET", link)
resp = session.MakeRequest(t, req, http.StatusOK) resp = session.MakeRequest(t, req, http.StatusOK)
// Step3: fill the form of the create // Step3: test and submit form
htmlDoc = NewHTMLParser(t, resp.Body) htmlDoc = NewHTMLParser(t, resp.Body)
link, exists = htmlDoc.doc.Find("form.ui.form[action^=\"/repo/create\"]").Attr("action") assertRepoCreateForm(t, htmlDoc, user, templateID)
assert.True(t, exists, "The template has changed")
_, exists = htmlDoc.doc.Find(fmt.Sprintf(".owner.dropdown .item[data-value=\"%d\"]", generateOwner.ID)).Attr("data-value")
assert.True(t, exists, fmt.Sprintf("Generate owner '%s' is not present in select box", generateOwnerName))
req = NewRequestWithValues(t, "POST", link, map[string]string{ req = NewRequestWithValues(t, "POST", link, map[string]string{
"_csrf": htmlDoc.GetCSRF(), "_csrf": htmlDoc.GetCSRF(),
"uid": fmt.Sprintf("%d", generateOwner.ID), "uid": fmt.Sprintf("%d", generateOwner.ID),
@ -52,41 +61,66 @@ func testRepoGenerate(t *testing.T, session *TestSession, templateID, templateOw
session.MakeRequest(t, req, http.StatusSeeOther) session.MakeRequest(t, req, http.StatusSeeOther)
// Step4: check the existence of the generated repo // Step4: check the existence of the generated repo
req = NewRequestf(t, "GET", "/%s/%s", generateOwnerName, generateRepoName) req = NewRequestf(t, "GET", "/%s/%s", generateOwner.Name, generateRepoName)
session.MakeRequest(t, req, http.StatusOK) session.MakeRequest(t, req, http.StatusOK)
// Step5: check substituted values in Readme // Step5: check substituted values in Readme
req = NewRequestf(t, "GET", "/%s/%s/raw/branch/master/README.md", generateOwnerName, generateRepoName) req = NewRequestf(t, "GET", "/%s/%s/raw/branch/master/README.md", generateOwner.Name, generateRepoName)
resp = session.MakeRequest(t, req, http.StatusOK) resp = session.MakeRequest(t, req, http.StatusOK)
body := fmt.Sprintf(`# %s Readme body := fmt.Sprintf(`# %s Readme
Owner: %s Owner: %s
Link: /%s/%s Link: /%s/%s
Clone URL: %s%s/%s.git`, Clone URL: %s%s/%s.git`,
generateRepoName, generateRepoName,
strings.ToUpper(generateOwnerName), strings.ToUpper(generateOwner.Name),
generateOwnerName, generateOwner.Name,
generateRepoName, generateRepoName,
setting.AppURL, setting.AppURL,
generateOwnerName, generateOwner.Name,
generateRepoName) generateRepoName)
assert.Equal(t, body, resp.Body.String()) assert.Equal(t, body, resp.Body.String())
// Step6: check substituted values in substituted file path ${REPO_NAME} // Step6: check substituted values in substituted file path ${REPO_NAME}
req = NewRequestf(t, "GET", "/%s/%s/raw/branch/master/%s.log", generateOwnerName, generateRepoName, generateRepoName) req = NewRequestf(t, "GET", "/%s/%s/raw/branch/master/%s.log", generateOwner.Name, generateRepoName, generateRepoName)
resp = session.MakeRequest(t, req, http.StatusOK) resp = session.MakeRequest(t, req, http.StatusOK)
assert.Equal(t, generateRepoName, resp.Body.String()) assert.Equal(t, generateRepoName, resp.Body.String())
}
return resp // test form elements before and after POST error response
func TestRepoCreateForm(t *testing.T) {
defer tests.PrepareTestEnv(t)()
userName := "user1"
session := loginUser(t, userName)
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: userName})
req := NewRequest(t, "GET", "/repo/create")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
assertRepoCreateForm(t, htmlDoc, user, "")
req = NewRequestWithValues(t, "POST", "/repo/create", map[string]string{
"_csrf": htmlDoc.GetCSRF(),
})
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body)
assertRepoCreateForm(t, htmlDoc, user, "")
} }
func TestRepoGenerate(t *testing.T) { func TestRepoGenerate(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user1") userName := "user1"
testRepoGenerate(t, session, "44", "user27", "template1", "user1", "generated1") session := loginUser(t, userName)
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: userName})
testRepoGenerate(t, session, "44", "user27", "template1", user, user, "generated1")
} }
func TestRepoGenerateToOrg(t *testing.T) { func TestRepoGenerateToOrg(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2") userName := "user2"
testRepoGenerate(t, session, "44", "user27", "template1", "user2", "generated2") session := loginUser(t, userName)
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: userName})
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"})
testRepoGenerate(t, session, "44", "user27", "template1", user, org, "generated2")
} }