466 lines
19 KiB
Go
466 lines
19 KiB
Go
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(`<input type="hidden" name="_csrf" value="x">`),
|
||
"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)
|
||
}
|
||
// 缺注入时模板不报错,只会渲染出 <no value> 让整段脚本失效,必须显式断言
|
||
if strings.Contains(out, "<no value>") {
|
||
t.Fatal("rendered page contains <no value>: MaxCustomTextLen was not injected")
|
||
}
|
||
// 注意:html/template 在 <script> 上下文会主动插入安全空格,
|
||
// 实际输出形如 `var MAX_CUSTOM_LEN = 200 ;`,因此用正则而非字面量匹配。
|
||
want := regexp.MustCompile(`var MAX_CUSTOM_LEN\s*=\s*` + strconv.Itoa(conf.Limits.CustomAnswerLen) + `\s*;`)
|
||
if !want.MatchString(out) {
|
||
t.Fatalf("missing custom text limit constant, want /%s/", want)
|
||
}
|
||
if !strings.Contains(out, "data-tb-text-counter") {
|
||
t.Fatal("custom inputs are missing the data-tb-text-counter hook")
|
||
}
|
||
})
|
||
|
||
t.Run("form can vote", 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: `["满意","一般"]`},
|
||
{Model: dbutil.Model{ID: 3}, FieldType: db.VoteFormFieldTypeCheckbox, Label: "喜欢的", Options: `["A","B","C"]`},
|
||
{Model: dbutil.Model{ID: 4}, FieldType: db.VoteFormFieldTypeRating, Label: "评分"},
|
||
{Model: dbutil.Model{ID: 5}, FieldType: db.VoteFormFieldTypeRank, Label: "排序", Options: `["首选","次选","末选"]`},
|
||
}
|
||
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 (form) failed: %v", err)
|
||
}
|
||
})
|
||
|
||
t.Run("already voted with results", func(t *testing.T) {
|
||
base := voteBaseData()
|
||
base["Vote"] = &db.Vote{Title: "已投", Type: db.VoteTypeSingle, LinkToken: "abc123"}
|
||
base["Owner"] = map[string]interface{}{"Name": "tester", "Domain": "test", "Avatar": ""}
|
||
base["Options"] = []*db.VoteOption{opt1, opt2}
|
||
base["HasVoted"] = true
|
||
base["CanVote"] = false
|
||
base["NotStarted"] = false
|
||
base["Ended"] = false
|
||
base["VoteScopeRegisteredOnly"] = false
|
||
base["ShowResults"] = true
|
||
base["IsOwner"] = false
|
||
base["Results"] = map[string]interface{}{
|
||
"Type": "single",
|
||
"Total": int64(2),
|
||
"Options": []interface{}{},
|
||
}
|
||
if err := renderWithData(t, "vote/page", base); err != nil {
|
||
t.Fatalf("render vote/page (voted) failed: %v", err)
|
||
}
|
||
})
|
||
|
||
t.Run("not started", func(t *testing.T) {
|
||
base := voteBaseData()
|
||
base["Vote"] = &db.Vote{Title: "未开始", Type: db.VoteTypeSingle, LinkToken: "abc123"}
|
||
base["Owner"] = map[string]interface{}{"Name": "tester", "Domain": "test", "Avatar": ""}
|
||
base["HasVoted"] = false
|
||
base["CanVote"] = false
|
||
base["NotStarted"] = true
|
||
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 (not started) failed: %v", err)
|
||
}
|
||
})
|
||
}
|
||
|
||
func TestRenderVoteManagementPages(t *testing.T) {
|
||
t.Run("question list votes panel", func(t *testing.T) {
|
||
base := voteBaseData()
|
||
base["IsUserPage"] = true
|
||
// 提问箱页面渲染所需的字段(与 QuestionList 提供的一致)
|
||
base["Stats"] = map[string]int64{"total": 0, "answered": 0, "unanswered": 0, "private": 0, "pinned": 0, "chats": 0}
|
||
base["Keyword"] = ""
|
||
base["SelectedSort"] = "latest"
|
||
base["SelectedStatus"] = "all"
|
||
base["SelectedType"] = "all"
|
||
base["ReceivedItems"] = []interface{}{}
|
||
base["ConversationDetails"] = []interface{}{}
|
||
// 投票面板数据:投票管理已合并到「我的提问箱 · 投票」面板
|
||
base["VoteStats"] = map[string]int64{"total": 4, "published": 2}
|
||
base["MyVotes"] = []interface{}{
|
||
map[string]interface{}{
|
||
"ID": 1, "Title": "投票 1", "Type": "single", "Status": "draft", "EffectiveStatus": "draft",
|
||
"SubmissionCount": int64(0), "OptionCount": int64(2),
|
||
"CreatedAt": time.Now(), "LinkToken": "abc",
|
||
},
|
||
map[string]interface{}{
|
||
"ID": 2, "Title": "投票 2", "Type": "multiple", "Status": "published", "EffectiveStatus": "ended",
|
||
"SubmissionCount": int64(5), "OptionCount": int64(3),
|
||
"CreatedAt": time.Now(), "LinkToken": "def",
|
||
},
|
||
map[string]interface{}{
|
||
"ID": 3, "Title": "投票 3", "Type": "single", "Status": "published", "EffectiveStatus": "published",
|
||
"SubmissionCount": int64(2), "OptionCount": int64(2),
|
||
"CreatedAt": time.Now(), "LinkToken": "ghi", "RemainingText": "剩余 2 天 3 小时",
|
||
},
|
||
map[string]interface{}{
|
||
"ID": 4, "Title": "投票 4", "Type": "form", "Status": "published", "EffectiveStatus": "published",
|
||
"SubmissionCount": int64(1), "OptionCount": int64(0),
|
||
"CreatedAt": time.Now(), "LinkToken": "jkl", "RemainingText": "无限期",
|
||
},
|
||
}
|
||
if err := renderWithData(t, "user/question-list", base); err != nil {
|
||
t.Fatalf("render user/question-list (votes panel) failed: %v", err)
|
||
}
|
||
})
|
||
|
||
t.Run("vote form new", func(t *testing.T) {
|
||
base := voteBaseData()
|
||
base["IsUserPage"] = true
|
||
base["IsEdit"] = false
|
||
out, err := renderToString(t, "user/vote-form", base)
|
||
if err != nil {
|
||
t.Fatalf("render user/vote-form (new) failed: %v", err)
|
||
}
|
||
// 站长侧选项上限常量已注入并应用到两类选项输入(单选/多选选项 + 字段选项)。
|
||
// 缺注入时渲染出的会是 var MAX_OPTION_LEN = <no value>;,两个 maxlength 也失去同源上限。
|
||
wantVar := regexp.MustCompile(`var MAX_OPTION_LEN\s*=\s*` + strconv.Itoa(conf.Limits.VoteOptionLen) + `\s*;`)
|
||
if !wantVar.MatchString(out) {
|
||
t.Fatalf("vote-form: 缺 MAX_OPTION_LEN 注入,want %d", conf.Limits.VoteOptionLen)
|
||
}
|
||
wantMax := `maxlength="' + MAX_OPTION_LEN + '"`
|
||
if n := strings.Count(out, wantMax); n != 2 {
|
||
t.Fatalf("vote-form: 选项输入框应有两处引用 MAX_OPTION_LEN,实际 %d 处", n)
|
||
}
|
||
})
|
||
|
||
t.Run("vote form edit", func(t *testing.T) {
|
||
base := voteBaseData()
|
||
base["IsUserPage"] = true
|
||
base["IsEdit"] = true
|
||
base["Vote"] = &db.Vote{Model: dbutil.Model{ID: 1}, Title: "编辑中", Type: db.VoteTypeMultiple, Visibility: "public",
|
||
VoterScope: "anyone", ShowResult: "after_vote", MaxChoices: 3, MaxVotesPerFingerprint: 1, RequireCaptcha: true}
|
||
base["InitialOptions"] = []string{"A", "B"}
|
||
base["InitialFields"] = []interface{}{}
|
||
if err := renderWithData(t, "user/vote-form", base); err != nil {
|
||
t.Fatalf("render user/vote-form (edit) failed: %v", err)
|
||
}
|
||
})
|
||
|
||
t.Run("vote form edit locked", func(t *testing.T) {
|
||
base := voteBaseData()
|
||
base["IsUserPage"] = true
|
||
base["IsEdit"] = true
|
||
base["LockedContent"] = true
|
||
base["Vote"] = &db.Vote{Model: dbutil.Model{ID: 1}, Title: "进行中", Type: db.VoteTypeSingle, Visibility: "public",
|
||
VoterScope: "anyone", ShowResult: "after_vote", MaxChoices: 1, MaxVotesPerFingerprint: 1, RequireCaptcha: true}
|
||
base["InitialOptions"] = []string{"A", "B"}
|
||
base["InitialFields"] = []interface{}{}
|
||
if err := renderWithData(t, "user/vote-form", base); err != nil {
|
||
t.Fatalf("render user/vote-form (locked) failed: %v", err)
|
||
}
|
||
})
|
||
|
||
t.Run("vote results single", func(t *testing.T) {
|
||
base := voteBaseData()
|
||
base["IsUserPage"] = true
|
||
base["Vote"] = &db.Vote{Model: dbutil.Model{ID: 1}, Title: "结果", Type: db.VoteTypeSingle}
|
||
base["Total"] = int64(2)
|
||
base["Results"] = map[string]interface{}{
|
||
"Type": "single",
|
||
"Options": []interface{}{
|
||
map[string]interface{}{"Option": map[string]interface{}{"Content": "A"}, "Count": int64(2), "Percent": 100},
|
||
},
|
||
"CustomStats": []interface{}{
|
||
map[string]interface{}{"Text": "自定义项", "Count": int64(1)},
|
||
},
|
||
}
|
||
if err := renderWithData(t, "user/vote-results", base); err != nil {
|
||
t.Fatalf("render user/vote-results (single) failed: %v", err)
|
||
}
|
||
})
|
||
|
||
t.Run("vote results form", func(t *testing.T) {
|
||
base := voteBaseData()
|
||
base["IsUserPage"] = true
|
||
base["Vote"] = &db.Vote{Model: dbutil.Model{ID: 1}, Title: "表单结果", Type: db.VoteTypeForm}
|
||
base["Total"] = int64(1)
|
||
base["FormFields"] = []*db.VoteFormField{
|
||
{Label: "昵称", FieldType: db.VoteFormFieldTypeText},
|
||
{Label: "满意度", FieldType: db.VoteFormFieldTypeRadio, Options: `["满意","一般"]`},
|
||
}
|
||
base["Results"] = map[string]interface{}{
|
||
"Type": "form", "FormCount": int64(1), "FormFieldStats": []interface{}{
|
||
map[string]interface{}{
|
||
"Field": map[string]interface{}{"Label": "最喜欢的水果", "FieldType": "radio", "Options": `["苹果","香蕉"]`},
|
||
"Total": int64(1),
|
||
"OptionStats": []interface{}{
|
||
map[string]interface{}{"Option": map[string]interface{}{"Content": "苹果"}, "Count": int64(1)},
|
||
map[string]interface{}{"Option": map[string]interface{}{"Content": "香蕉"}, "Count": int64(0)},
|
||
},
|
||
},
|
||
map[string]interface{}{
|
||
"Field": map[string]interface{}{"Label": "整体评价", "FieldType": "rating"},
|
||
"Total": int64(1), "AvgRating": 9.0,
|
||
"RatingCounts": []interface{}{},
|
||
},
|
||
map[string]interface{}{
|
||
"Field": map[string]interface{}{"Label": "按喜好排序", "FieldType": "rank"},
|
||
"Total": int64(1), "AvgRank": 1.5,
|
||
"OptionStats": []interface{}{
|
||
map[string]interface{}{"Option": map[string]interface{}{"Content": "A"}, "Count": int64(1), "AvgRank": 1.0},
|
||
map[string]interface{}{"Option": map[string]interface{}{"Content": "B"}, "Count": int64(1), "AvgRank": 2.0},
|
||
},
|
||
},
|
||
map[string]interface{}{
|
||
"Field": map[string]interface{}{"Label": "建议", "FieldType": "textarea"},
|
||
"Total": int64(1), "TextCount": int64(1),
|
||
},
|
||
},
|
||
}
|
||
// 文本类字段逐条提交内容按字段标签归类(内联在题目下方折叠展示)
|
||
base["TextReplies"] = map[string]interface{}{
|
||
"建议": []interface{}{
|
||
map[string]interface{}{"CreatedAt": time.Now(), "Text": "希望支持深色模式"},
|
||
},
|
||
}
|
||
if err := renderWithData(t, "user/vote-results", base); err != nil {
|
||
t.Fatalf("render user/vote-results (form) failed: %v", err)
|
||
}
|
||
})
|
||
}
|
||
|
||
func TestRenderQuestionListWithVotes(t *testing.T) {
|
||
base := voteBaseData()
|
||
base["PageUser"] = map[string]interface{}{"Domain": "test", "Name": "tester", "Avatar": "",
|
||
"Background": "", "Intro": "", "UseCustomQRColor": false, "Qrcodebackcolor": "", "Qrcodecolor": "",
|
||
"Dotscale": "", "BackgroundImage": "", "Backgroundimagealpha": "", "Qrcodepdpcolor": "",
|
||
"Acfunlink": "", "Bililink": "", "Weibolink": "", "Xiaohongshulink": "", "Bloglink": "",
|
||
"Usernamecolor": "#000000", "Introcolor": "#000000"}
|
||
base["CanAsk"] = true
|
||
base["VoteEnabled"] = true
|
||
base["PageVotes"] = []*db.Vote{{Title: "板块投票", LinkToken: "abc123"}}
|
||
base["PageQuestions"] = []interface{}{}
|
||
base["AnsweredCount"] = int64(0)
|
||
base["PageQuestionCursor"] = ""
|
||
base["AllowChat"] = false
|
||
base["ChatMaxMessages"] = 20
|
||
if err := renderWithData(t, "question/list", base); err != nil {
|
||
t.Fatalf("render question/list with votes failed: %v", err)
|
||
}
|
||
|
||
// 箱主本人访问自己的提问箱:无进行中投票时也应显示管理入口(空态)
|
||
base["IsOwnPage"] = true
|
||
base["PageVotes"] = []interface{}{}
|
||
if err := renderWithData(t, "question/list", base); err != nil {
|
||
t.Fatalf("render question/list (own page, empty) failed: %v", err)
|
||
}
|
||
}
|
||
|
||
func TestRenderExploreWithHotVotes(t *testing.T) {
|
||
base := voteBaseData()
|
||
base["Users"] = []interface{}{}
|
||
base["RecommendUsers"] = []interface{}{}
|
||
base["HotTags"] = []string{}
|
||
base["Keyword"] = ""
|
||
base["Sort"] = "latest"
|
||
base["Page"] = 1
|
||
base["TotalPages"] = 1
|
||
base["PrevPage"] = 0
|
||
base["NextPage"] = 2
|
||
base["HotVotes"] = []*db.Vote{
|
||
{Title: "热门投票 1", LinkToken: "abc1", Type: db.VoteTypeSingle},
|
||
{Title: "热门投票 2", LinkToken: "abc2", Type: db.VoteTypeForm},
|
||
}
|
||
base["AllVotes"] = []*db.Vote{
|
||
{Title: "全部投票 1", LinkToken: "abc3", Type: db.VoteTypeMultiple, Model: dbutil.Model{CreatedAt: time.Now()}},
|
||
}
|
||
if err := renderWithData(t, "user/explore", base); err != nil {
|
||
t.Fatalf("render user/explore with hot votes failed: %v", err)
|
||
}
|
||
}
|