This commit is contained in:
tamakyi
2026-09-08 10:10:44 +08:00
commit 2ed27dd55e
288 changed files with 67061 additions and 0 deletions
+271
View File
@@ -0,0 +1,271 @@
package templates
import (
gotemplate "html/template"
"strings"
"testing"
"time"
"github.com/tamakyi/TamaBox/internal/conf"
)
type mockPageQuestion struct {
ID uint
Content string
CreatedAt time.Time
IsPinned bool
Answer string
}
func publicBaseData() map[string]interface{} {
pageUser := map[string]interface{}{
"Domain": "tester", "Name": "测试箱主", "Avatar": "", "Background": "",
"Intro": "欢迎提问", "Usernamecolor": "#000", "Introcolor": "#666",
"Acfunlink": "none", "Bililink": "none", "Weibolink": "none",
"Xiaohongshulink": "none", "Bloglink": "none",
"Qrcodebackcolor": "#fff", "Qrcodecolor": "#000", "Dotscale": "0.35",
"BackgroundImage": "", "Backgroundimagealpha": "0.8", "Qrcodepdpcolor": "#000",
"HarassmentSetting": "disabled", "AllowChat": true, "ChatMaxMessages": 10,
}
return map[string]interface{}{
"LangCode": "zh-CN",
"SupportedLangs": []map[string]string{{"Code": "zh-CN", "Name": "简体中文"}},
"IsLogged": false,
"IsUserPage": true,
"IsOwnPage": false,
"IsPixel": false,
"VConsole": false,
"CSRFTokenHTML": gotemplate.HTML(`<input type="hidden" name="_csrf" value="x">`),
"RecaptchaSiteKey": "",
"CaptchaType": "recaptcha",
"PageUser": pageUser,
"PageUserRaw": pageUser,
"PageQuestions": []mockPageQuestion{
{ID: 1, Content: "置顶的问题", CreatedAt: time.Now(), IsPinned: true, Answer: "答1"},
{ID: 2, Content: "普通问题", CreatedAt: time.Now().Add(-time.Hour), IsPinned: false, Answer: "答2"},
},
"PageQuestionCursor": "2",
"CanAsk": true,
"AnsweredCount": 2,
"ICP": "",
// [limits] 全站上限(生产由 Contexter 全局注入 conf.Limits),供提问/回答/追问
// 输入框 maxlength 与私聊发起框同源
"Limits": conf.Limits,
}
}
// TestRenderPublicQuestionList 渲染提问箱主页(含置顶条目),复现公开页 500。
func TestRenderPublicQuestionList(t *testing.T) {
if err := renderWithData(t, "question/list", publicBaseData()); err != nil {
t.Fatalf("render question/list failed: %v", err)
}
}
// TestRenderQuestionListClosed 提问功能关闭时:展示关闭原因 + 下次开放/上次关闭时间(问题 3 的模板路径)。
func TestRenderQuestionListClosed(t *testing.T) {
base := publicBaseData()
base["CanAsk"] = false
base["AskState"] = map[string]interface{}{"Open": false, "Reason": "not_started"}
base["AskClosedMessage"] = "提问功能尚未开放"
base["AskNextOpenAtStr"] = "2026-09-02 10:00"
base["AskLastCloseAtStr"] = "2026-09-01 18:00"
out, err := renderToString(t, "question/list", base)
if err != nil {
t.Fatalf("render question/list (closed) failed: %v", err)
}
if !strings.Contains(out, "提问功能尚未开放") {
t.Fatal("closed reason not rendered")
}
if !strings.Contains(out, "下次开放时间:2026-09-02 10:00") {
t.Fatal("next open time not rendered")
}
if !strings.Contains(out, "上次关闭时间:2026-09-01 18:00") {
t.Fatal("last close time not rendered")
}
}
// TestRenderQuestionListClosedPermanent 一次性窗口结束后(无循环定时):只展示上次关闭时间,不展示下次开放。
func TestRenderQuestionListClosedPermanent(t *testing.T) {
base := publicBaseData()
base["CanAsk"] = false
base["AskState"] = map[string]interface{}{"Open": false, "Reason": "ended"}
base["AskClosedMessage"] = "提问功能已结束收集"
base["AskNextOpenAtStr"] = ""
base["AskLastCloseAtStr"] = "2026-08-31 18:00"
out, err := renderToString(t, "question/list", base)
if err != nil {
t.Fatalf("render question/list (ended) failed: %v", err)
}
if strings.Contains(out, "下次开放时间") {
t.Fatal("next open time should NOT be rendered when permanently closed")
}
if !strings.Contains(out, "上次关闭时间:2026-08-31 18:00") {
t.Fatal("last close time not rendered")
}
}
// TestRenderQuestionListRegisterOnlyCaption 未开启「仅允许注册用户」时,二维码文案保持原样。
func TestRenderQuestionListRegisterOnlyCaption(t *testing.T) {
base := publicBaseData()
base["RegisterOnlyAsk"] = false
out, err := renderToString(t, "question/list", base)
if err != nil {
t.Fatalf("render question/list failed: %v", err)
}
if !strings.Contains(out, "谁都可以以匿名的形式提问") {
t.Fatal("anonymous caption should be rendered when not register-only")
}
if strings.Contains(out, "仅允许注册用户向TA提问") {
t.Fatal("register-only caption should NOT be rendered when not register-only")
}
}
// TestRenderQuestionListRegisterOnlyOn 开启「仅允许注册用户」(箱主登录态可提问)时,二维码文案联动(问题 2)。
func TestRenderQuestionListRegisterOnlyOn(t *testing.T) {
base := publicBaseData()
base["CanAsk"] = true
base["RegisterOnlyAsk"] = true
out, err := renderToString(t, "question/list", base)
if err != nil {
t.Fatalf("render question/list failed: %v", err)
}
if !strings.Contains(out, "仅允许注册用户向TA提问") {
t.Fatal("register-only caption should be rendered when register-only enabled")
}
if strings.Contains(out, "谁都可以以匿名的形式提问") {
t.Fatal("anonymous caption should NOT be rendered when register-only enabled")
}
}
// TestRenderQuestionListVoteDisabled 投票功能关闭时:投票 Tab 保留显示但置灰禁用,并带原因 tooltip。
func TestRenderQuestionListVoteDisabled(t *testing.T) {
base := publicBaseData()
base["VoteEnabled"] = false
base["VoteClosedMessage"] = "该提问箱已关闭投票功能"
out, err := renderToString(t, "question/list", base)
if err != nil {
t.Fatalf("render question/list (vote disabled) failed: %v", err)
}
if !strings.Contains(out, "profile-tab is-disabled") {
t.Fatal("vote tab should stay visible but disabled")
}
if !strings.Contains(out, "disabled") {
t.Fatal("vote tab should carry disabled attribute")
}
if !strings.Contains(out, "该提问箱已关闭投票功能") {
t.Fatal("vote tab should carry the close reason tooltip")
}
// 注意:JS 守卫代码里也有 [data-profile-tab="votes"] 选择器,须按按钮元素精确断言
if strings.Contains(out, `data-profile-tab="votes" role="tab"`) {
t.Fatal("disabled vote tab must NOT be switchable")
}
}
// TestRenderQuestionListVoteEnabled 投票功能正常时:投票 Tab 可点击切换。
func TestRenderQuestionListVoteEnabled(t *testing.T) {
base := publicBaseData()
base["VoteEnabled"] = true
out, err := renderToString(t, "question/list", base)
if err != nil {
t.Fatalf("render question/list (vote enabled) failed: %v", err)
}
if !strings.Contains(out, `data-profile-tab="votes"`) {
t.Fatal("vote tab should be switchable when enabled")
}
if strings.Contains(out, "profile-tab is-disabled") {
t.Fatal("vote tab must not be disabled when enabled")
}
}
// TestRenderQuestionListChatOnly 提问功能关闭但私聊开启时:保留独立私聊入口(含已登录/未登录两种形态)。
func TestRenderQuestionListChatOnly(t *testing.T) {
base := publicBaseData()
base["CanAsk"] = false
base["AskState"] = map[string]interface{}{"Open": false, "Reason": "disabled"}
base["AskClosedMessage"] = "提问箱主人已关闭提问功能"
base["AskNextOpenAtStr"] = ""
base["AskLastCloseAtStr"] = ""
base["AllowChat"] = true
base["ChatMaxMessages"] = 20
base["LoggedUser"] = map[string]interface{}{"Domain": "tester", "Name": "测试箱主"}
// 已登录:渲染独立私聊表单
out, err := renderToString(t, "question/list", base)
if err != nil {
t.Fatalf("render question/list (chat only, logged in) failed: %v", err)
}
if !strings.Contains(out, "提问箱主人已关闭提问功能") {
t.Fatal("ask closed reason should still be shown")
}
if !strings.Contains(out, `id="chat-only-form"`) {
t.Fatal("chat-only form should be rendered when chat enabled and logged in")
}
if !strings.Contains(out, `id="chat-only-form" enctype="multipart/form-data"`) {
t.Fatal("chat-only form must be multipart, otherwise form.Bind panics on the Images field")
}
if !strings.Contains(out, `name="start_chat_val" value="true"`) {
t.Fatal("chat-only form must submit as a chat request")
}
if !strings.Contains(out, "在此处撰写你的私聊消息...") {
t.Fatal("chat placeholder should be rendered")
}
if !strings.Contains(out, "onChatOnlySubmit") {
t.Fatal("chat-only captcha callback should be defined for non-math captcha")
}
// 未登录:渲染登录引导而非表单
base["LoggedUser"] = nil
out, err = renderToString(t, "question/list", base)
if err != nil {
t.Fatalf("render question/list (chat only, anonymous) failed: %v", err)
}
if strings.Contains(out, `id="chat-only-form"`) {
t.Fatal("chat-only form must not be rendered for anonymous visitors")
}
if !strings.Contains(out, "/login?to=") {
t.Fatal("anonymous visitors should get a login link to start chats")
}
}
// TestRenderQuestionListChatOnlyNoChat 提问关闭且私聊也关闭:不渲染私聊入口。
func TestRenderQuestionListChatOnlyNoChat(t *testing.T) {
base := publicBaseData()
base["CanAsk"] = false
base["AskClosedMessage"] = "提问箱主人已关闭提问功能"
base["AllowChat"] = false
base["LoggedUser"] = map[string]interface{}{"Domain": "tester", "Name": "测试箱主"}
out, err := renderToString(t, "question/list", base)
if err != nil {
t.Fatalf("render question/list (all closed) failed: %v", err)
}
if strings.Contains(out, `id="chat-only-form"`) {
t.Fatal("chat-only form must not be rendered when chat is disabled")
}
}
// TestRenderPublicQuestionItem 渲染单题页(含历史列表),复现公开页 500。
func TestRenderPublicQuestionItem(t *testing.T) {
data := publicBaseData()
data["Question"] = map[string]interface{}{
"ID": 1, "Content": "置顶的问题", "Answer": "答1", "CreatedAt": time.Now(),
"UpdatedAt": time.Now(), "IsPrivate": false, "IsPinned": true,
"Token": "abc123", "ReceiveReplyEmail": "",
}
data["QuestionToken"] = "abc123"
data["AskUploadImages"] = []interface{}{}
data["AnswerUploadImages"] = []interface{}{}
data["FollowUps"] = []interface{}{}
data["CanFollowUp"] = false
data["FollowUpRemaining"] = 0
data["FollowUpLimit"] = 3
data["IsOwnPage"] = true
data["IsQuestioner"] = false
data["IsBlocked"] = false
data["AllowChat"] = true
if err := renderWithData(t, "question/item", data); err != nil {
t.Fatalf("render question/item failed: %v", err)
}
}