package templates import ( gotemplate "html/template" "regexp" "strconv" "strings" "testing" "time" "github.com/flamego/template" "github.com/tamakyi/TamaBox/internal/conf" "github.com/tamakyi/TamaBox/internal/db" "github.com/tamakyi/TamaBox/internal/dbutil" ) // voteBaseData 构造投票模板渲染的基础数据(仿 render_test.go 的 base)。 func voteBaseData() map[string]interface{} { return map[string]interface{}{ "Title": "投票", "LangCode": "zh-CN", "SupportedLangs": []map[string]string{{"Code": "zh-CN", "Name": "简体中文"}}, "IsLogged": true, "LoggedUser": map[string]interface{}{"Domain": "test", "Name": "tester"}, "LoggedUserName": "tester", "CurrentURI": "/vote/abc123", "IsUserPage": false, "IsPixel": false, "VConsole": false, "CSRFTokenHTML": gotemplate.HTML(``), "RecaptchaSiteKey": "", "CaptchaType": "recaptcha", "PageUser": map[string]interface{}{"Domain": "test", "Name": "tester", "Avatar": ""}, "ICP": "", // 参与端自定义补充选项的字数上限(生产由 route/vote 注入 conf.Limits.CustomAnswerLen) "MaxCustomTextLen": conf.Limits.CustomAnswerLen, "CustomFieldMarker": db.CustomFieldMarker, // 站长侧单个选项文本上限(生产由 route/user 注入 conf.Limits.VoteOptionLen) "MaxVoteOptionLen": conf.Limits.VoteOptionLen, // [limits] 全站上限(生产由 Contexter 全局注入 conf.Limits), // 供 question/list 嵌套的提问框 maxlength 同源 "Limits": conf.Limits, } } func TestVoteFormUsesFixedDeviceLimitNotice(t *testing.T) { fs, err := template.EmbedFS(FS, ".", []string{".html"}) if err != nil { t.Fatalf("EmbedFS: %v", err) } for _, f := range fs.Files() { if f.Name() != "user/vote-form" && f.Name() != "user/vote-form.html" { continue } data, err := f.Data() if err != nil { t.Fatalf("get template data: %v", err) } source := string(data) if strings.Contains(source, `name="max_votes_per_fingerprint"`) { t.Fatal("fixed fingerprint limit must not be rendered as a form input") } if !strings.Contains(source, `vote.formMaxVotesPerFingerprint`) { t.Fatal("fixed fingerprint limit notice is missing") } return } t.Fatal("user/vote-form.html not found") } func TestRenderVotePage(t *testing.T) { opt1 := &db.VoteOption{Content: "选项 A"} opt2 := &db.VoteOption{Content: "选项 B"} t.Run("single can vote", func(t *testing.T) { base := voteBaseData() base["Vote"] = &db.Vote{Title: "测试单选", Description: "描述", Type: db.VoteTypeSingle, LinkToken: "abc123", MaxChoices: 1} base["Owner"] = map[string]interface{}{"Name": "tester", "Domain": "test", "Avatar": ""} base["Options"] = []*db.VoteOption{opt1, opt2} base["HasVoted"] = false base["CanVote"] = true base["NotStarted"] = false base["Ended"] = false base["VoteScopeRegisteredOnly"] = false base["ShowResults"] = false base["IsOwner"] = false if err := renderWithData(t, "vote/page", base); err != nil { t.Fatalf("render vote/page (single) failed: %v", err) } }) t.Run("multiple can vote", func(t *testing.T) { base := voteBaseData() base["Vote"] = &db.Vote{Title: "多选", Type: db.VoteTypeMultiple, LinkToken: "abc123", MaxChoices: 2} base["Owner"] = map[string]interface{}{"Name": "tester", "Domain": "test", "Avatar": ""} base["Options"] = []*db.VoteOption{opt1, opt2, {Content: "选项 C"}} base["HasVoted"] = false base["CanVote"] = true base["NotStarted"] = false base["Ended"] = false base["VoteScopeRegisteredOnly"] = false base["ShowResults"] = false base["IsOwner"] = false if err := renderWithData(t, "vote/page", base); err != nil { t.Fatalf("render vote/page (multiple) failed: %v", err) } }) t.Run("rating can vote", func(t *testing.T) { base := voteBaseData() base["Vote"] = &db.Vote{Title: "评分", Type: db.VoteTypeRating, LinkToken: "abc123"} base["Owner"] = map[string]interface{}{"Name": "tester", "Domain": "test", "Avatar": ""} base["HasVoted"] = false base["CanVote"] = true base["NotStarted"] = false base["Ended"] = false base["VoteScopeRegisteredOnly"] = false base["ShowResults"] = false base["IsOwner"] = false if err := renderWithData(t, "vote/page", base); err != nil { t.Fatalf("render vote/page (rating) failed: %v", err) } }) t.Run("rank can vote", func(t *testing.T) { base := voteBaseData() base["Vote"] = &db.Vote{Title: "排序", Type: db.VoteTypeRank, LinkToken: "abc123"} base["Owner"] = map[string]interface{}{"Name": "tester", "Domain": "test", "Avatar": ""} base["Options"] = []*db.VoteOption{opt1, opt2} base["HasVoted"] = false base["CanVote"] = true base["NotStarted"] = false base["Ended"] = false base["VoteScopeRegisteredOnly"] = false base["ShowResults"] = false base["IsOwner"] = false if err := renderWithData(t, "vote/page", base); err != nil { t.Fatalf("render vote/page (rank) failed: %v", err) } }) t.Run("preview draft form", func(t *testing.T) { base := voteBaseData() base["Vote"] = &db.Vote{Title: "草稿表单", Type: db.VoteTypeForm, LinkToken: "abc123"} base["Owner"] = map[string]interface{}{"Name": "tester", "Domain": "test", "Avatar": ""} base["FormFields"] = []*db.VoteFormField{ {Model: dbutil.Model{ID: 1}, FieldType: db.VoteFormFieldTypeText, Label: "昵称", Required: true, MaxLength: 20}, {Model: dbutil.Model{ID: 2}, FieldType: db.VoteFormFieldTypeRadio, Label: "满意度", Options: `["满意","一般"]`}, } base["HasVoted"] = false base["CanVote"] = true base["NotStarted"] = false base["Ended"] = false base["VoteScopeRegisteredOnly"] = false base["ShowResults"] = false base["IsOwner"] = true base["IsLogged"] = true base["PreviewMode"] = true if err := renderWithData(t, "vote/page", base); err != nil { t.Fatalf("render vote/page (preview) failed: %v", err) } }) t.Run("custom text limit injected", func(t *testing.T) { base := voteBaseData() base["Vote"] = &db.Vote{Title: "自定义选项", Type: db.VoteTypeSingle, LinkToken: "abc123", AllowCustom: true} base["Owner"] = map[string]interface{}{"Name": "tester", "Domain": "test", "Avatar": ""} base["FormFields"] = []*db.VoteFormField{ {Model: dbutil.Model{ID: 1}, FieldType: db.VoteFormFieldTypeRadio, Label: "满意度", Options: `["满意","一般"]`, AllowCustom: true}, } base["HasVoted"] = false base["CanVote"] = true base["NotStarted"] = false base["Ended"] = false base["VoteScopeRegisteredOnly"] = false base["ShowResults"] = false base["IsOwner"] = false out, err := renderToString(t, "vote/page", base) if err != nil { t.Fatalf("render vote/page (custom) failed: %v", err) } // 缺注入时模板不报错,只会渲染出 让整段脚本失效,必须显式断言 if strings.Contains(out, "") { t.Fatal("rendered page contains : MaxCustomTextLen was not injected") } // 注意:html/template 在