0904
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user