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 在