loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(ui): display user-friendly message for range error (#7420)

- Instead of displaying 'RangeError: Range' display 'x must be a number between $MIN and $MAX' when the validation fails for a range error check.
- Resolves forgejo/forgejo#3510
- Added integration testing.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7420
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-committed-by: Gusted <postmaster@gusted.xyz>

authored by

Gusted
Gusted
and committed by
Gusted
5706a245 15a2338f

+88 -2
+8
modules/web/middleware/binding.go
··· 79 79 return getRuleBody(field, "Include(") 80 80 } 81 81 82 + func GetRange(field reflect.StructField) (string, string) { 83 + min, max, _ := strings.Cut(getRuleBody(field, "Range("), ",") 84 + return min, max 85 + } 86 + 82 87 // Validate populates the data with validation error (if any). 83 88 func Validate(errs binding.Errors, data map[string]any, f any, l translation.Locale) binding.Errors { 84 89 if errs.Len() == 0 { ··· 131 136 data["ErrorMsg"] = trName + l.TrString("form.url_error", errs[0].Message) 132 137 case binding.ERR_INCLUDE: 133 138 data["ErrorMsg"] = trName + l.TrString("form.include_error", GetInclude(field)) 139 + case binding.ERR_RANGE: 140 + min, max := GetRange(field) 141 + data["ErrorMsg"] = trName + l.TrString("alert.range_error", l.PrettyNumber(min), l.PrettyNumber(max)) 134 142 case validation.ErrGlobPattern: 135 143 data["ErrorMsg"] = trName + l.TrString("form.glob_pattern_error", errs[0].Message) 136 144 case validation.ErrRegexPattern:
+1
options/locale_next/locale_en-US.json
··· 19 19 "themes.names.forgejo-dark": "Forgejo dark", 20 20 "error.not_found.title": "Page not found", 21 21 "alert.asset_load_failed": "Failed to load asset files from {path}. Please make sure the asset files can be accessed.", 22 + "alert.range_error": " must be a number between %[1]s and %[2]s.", 22 23 "settings.adopt": "Adopt", 23 24 "install.invalid_lfs_path": "Unable to create the LFS root at the specified path: %[1]s", 24 25 "install.lfs_jwt_secret_failed": "Unable to generate a LFS JWT secret: %[1]s"
+2 -2
services/forms/repo_form.go
··· 725 725 726 726 // AddTimeManuallyForm form that adds spent time manually. 727 727 type AddTimeManuallyForm struct { 728 - Hours int `binding:"Range(0,1000)"` 729 - Minutes int `binding:"Range(0,1000)"` 728 + Hours int `binding:"Range(0,1000)" locale:"repo.issues.add_time_hours"` 729 + Minutes int `binding:"Range(0,1000)" locale:"repo.issues.add_time_minutes"` 730 730 } 731 731 732 732 // Validate validates the fields
+77
tests/integration/issue_tracked_time_test.go
··· 1 + // Copyright 2025 The Forgejo Authors. All rights reserved. 2 + // SPDX-License-Identifier: GPL-3.0-or-later 3 + 4 + package integration 5 + 6 + import ( 7 + "net/http" 8 + "testing" 9 + 10 + issues_model "forgejo.org/models/issues" 11 + "forgejo.org/models/unittest" 12 + user_model "forgejo.org/models/user" 13 + forgejo_context "forgejo.org/services/context" 14 + "forgejo.org/tests" 15 + 16 + "github.com/stretchr/testify/assert" 17 + "github.com/stretchr/testify/require" 18 + ) 19 + 20 + func TestIssueAddTimeManually(t *testing.T) { 21 + defer tests.PrepareTestEnv(t)() 22 + 23 + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) 24 + session := loginUser(t, user2.Name) 25 + issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) 26 + require.NoError(t, issue2.LoadRepo(t.Context())) 27 + 28 + t.Run("No time", func(t *testing.T) { 29 + defer tests.PrintCurrentTest(t)() 30 + 31 + session.MakeRequest(t, NewRequestWithValues(t, "POST", issue2.Link()+"/times/add", map[string]string{ 32 + "_csrf": GetCSRF(t, session, issue2.Link()), 33 + }), http.StatusSeeOther) 34 + 35 + flashCookie := session.GetCookie(forgejo_context.CookieNameFlash) 36 + assert.NotNil(t, flashCookie) 37 + assert.Contains(t, flashCookie.Value, "error%3DNo%2Btime%2Bwas%2Bentered.") 38 + }) 39 + 40 + t.Run("Invalid hours", func(t *testing.T) { 41 + defer tests.PrintCurrentTest(t)() 42 + 43 + session.MakeRequest(t, NewRequestWithValues(t, "POST", issue2.Link()+"/times/add", map[string]string{ 44 + "_csrf": GetCSRF(t, session, issue2.Link()), 45 + "hours": "-1", 46 + }), http.StatusSeeOther) 47 + 48 + flashCookie := session.GetCookie(forgejo_context.CookieNameFlash) 49 + assert.NotNil(t, flashCookie) 50 + assert.Contains(t, flashCookie.Value, "error%3DHours%2Bmust%2Bbe%2Ba%2Bnumber%2Bbetween%2B0%2Band%2B1%252C000.") 51 + }) 52 + 53 + t.Run("Invalid minutes", func(t *testing.T) { 54 + defer tests.PrintCurrentTest(t)() 55 + 56 + session.MakeRequest(t, NewRequestWithValues(t, "POST", issue2.Link()+"/times/add", map[string]string{ 57 + "_csrf": GetCSRF(t, session, issue2.Link()), 58 + "minutes": "-1", 59 + }), http.StatusSeeOther) 60 + 61 + flashCookie := session.GetCookie(forgejo_context.CookieNameFlash) 62 + assert.NotNil(t, flashCookie) 63 + assert.Contains(t, flashCookie.Value, "error%3DMinutes%2Bmust%2Bbe%2Ba%2Bnumber%2Bbetween%2B0%2Band%2B1%252C000.") 64 + }) 65 + 66 + t.Run("Normal", func(t *testing.T) { 67 + defer tests.PrintCurrentTest(t)() 68 + 69 + session.MakeRequest(t, NewRequestWithValues(t, "POST", issue2.Link()+"/times/add", map[string]string{ 70 + "_csrf": GetCSRF(t, session, issue2.Link()), 71 + "hours": "3", 72 + "minutes": "14", 73 + }), http.StatusSeeOther) 74 + 75 + unittest.AssertExistsIf(t, true, &issues_model.TrackedTime{IssueID: issue2.ID, Time: 11640, UserID: user2.ID}) 76 + }) 77 + }