package tgrich import ( "encoding/json" "errors" "strings" "testing" "git.scuroneko.dev/scuroneko/laniakea/v2/tgapi" ) func TestRich103HTML(t *testing.T) { empty := "" for _, tc := range []struct { name string button tgapi.RichMessageButton fragment string }{ {"url", tgapi.RichMessageButton{URL: "https://example.com/?a=1&b=2"}, `url="https://example.com/?a=1&b=2"`}, {"callback", tgapi.RichMessageButton{CallbackData: `a"<&`, Style: tgapi.RichMessageButtonStyleLink}, `data="a"<&"`}, {"web app", tgapi.RichMessageButton{WebApp: &tgapi.WebAppInfo{URL: "https://example.com"}}, `type="web_app"`}, {"login", tgapi.RichMessageButton{LoginURL: &tgapi.LoginURL{URL: "https://example.com", ForwardText: "Forward", RequestWriteAccess: true}}, `request-write-access`}, {"inline", tgapi.RichMessageButton{SwitchInlineQuery: &empty}, `query=""`}, {"current", tgapi.RichMessageButton{SwitchInlineQueryCurrentChat: &empty}, `type="switch_inline_query_current_chat"`}, {"chosen", tgapi.RichMessageButton{SwitchInlineQueryChosenChat: &tgapi.SwitchInlineQueryChosenChat{AllowUserChats: true}}, `allow-user-chats`}, {"copy", tgapi.RichMessageButton{CopyText: &tgapi.CopyTextButton{Text: "copy"}}, `text="copy"`}, {"disabled", tgapi.RichMessageButton{Disabled: &tgapi.DisabledButton{}}, `type="disabled"`}, } { t.Run(tc.name, func(t *testing.T) { tc.button.Text = Text("") for _, block := range []tgapi.InputRichBlock{P(Button(tc.button)), ButtonsWithAlign([]tgapi.RichMessageButton{tc.button}, tgapi.RichBlockButtonCenter)} { msg, err := BuildHTML(block) if err != nil { t.Fatal(err) } if !strings.Contains(msg.HTML, tc.fragment) || !strings.Contains(msg.HTML, "<Go>") { t.Fatalf("unexpected HTML: %s", msg.HTML) } } }) } msg, err := BuildHTML(ExpandableBlockquoteWithCredit(Bold(Text("Quote")), Text("Author")), DocumentWithCaption(tgapi.InputMedia{Media: "attach://notes"}, Caption(Text("Notes"))), NewTable().SetCompact(true).Build()) if err != nil { t.Fatal(err) } for _, fragment := range []string{"
QuoteAuthor
", ``, ""} { if !strings.Contains(msg.HTML, fragment) { t.Fatalf("missing %s in %s", fragment, msg.HTML) } } if len(msg.Media) != 1 || msg.Media[0].Media.Type != tgapi.InputMediaTypeDocument || msg.Media[0].Media.Media != "attach://notes" { t.Fatalf("bad media: %#v", msg.Media) } if _, err := BuildDraftHTML(Document(tgapi.InputMedia{Media: "attach://notes"})); !errors.Is(err, tgapi.ErrRichMessageDraftUploadUnsupported) { t.Fatalf("draft: %v", err) } } func TestRich103Validation(t *testing.T) { good := tgapi.RichMessageButton{Text: Text("Go"), CallbackData: "x"} for _, n := range []int{0, 1, 8, 9} { buttons := make([]tgapi.RichMessageButton, n) for i := range buttons { buttons[i] = good } _, err := BuildHTML(Buttons(buttons)) if (err == nil) != (n >= 1 && n <= 8) { t.Fatalf("row %d: %v", n, err) } } for _, tc := range []struct { name string change func(*tgapi.RichMessageButton) }{ {"no action", func(b *tgapi.RichMessageButton) { b.CallbackData = "" }}, {"two actions", func(b *tgapi.RichMessageButton) { b.URL = "https://example.com" }}, {"long callback", func(b *tgapi.RichMessageButton) { b.CallbackData = strings.Repeat("я", 33) }}, {"formatting", func(b *tgapi.RichMessageButton) { b.Text = Bold(Text("Go")) }}, {"style", func(b *tgapi.RichMessageButton) { b.Style = "disable" }}, {"link url", func(b *tgapi.RichMessageButton) { b.CallbackData = ""; b.URL = "https://example.com"; b.Style = "link" }}, {"login bot", func(b *tgapi.RichMessageButton) { b.CallbackData = "" b.LoginURL = &tgapi.LoginURL{URL: "https://example.com", BotUsername: "other"} }}, } { t.Run(tc.name, func(t *testing.T) { b := good tc.change(&b) if _, err := BuildHTML(P(Button(b))); !errors.Is(err, ErrRichInvalidButton) { t.Fatalf("got %v", err) } }) } good.CallbackData = strings.Repeat("я", 32) if _, err := BuildHTML(P(Button(good))); err != nil { t.Fatal(err) } if _, err := BuildHTML(ButtonsWithAlign([]tgapi.RichMessageButton{good}, "bottom")); !errors.Is(err, ErrRichInvalidButton) { t.Fatalf("alignment: %v", err) } } func TestRich103InputJSON(t *testing.T) { for _, block := range []tgapi.InputRichBlock{ExpandableBlockquote(Text("quote")), Document(tgapi.InputMedia{Media: "file"}), Buttons([]tgapi.RichMessageButton{{Text: Text("Off"), Disabled: &tgapi.DisabledButton{}}})} { data, err := json.Marshal(block) if err != nil { t.Fatal(err) } var fields map[string]json.RawMessage if err := json.Unmarshal(data, &fields); err != nil { t.Fatal(err) } if len(fields["type"]) == 0 { t.Fatalf("missing discriminator: %s", data) } } } func TestRichButtonLabelEntitiesAndLimits(t *testing.T) { label := tgapi.RichTextArray{ Text("At "), tgapi.RichTextDateTime{Text: Text("noon"), UnixTime: 1000, DateTimeFormat: "t"}, tgapi.RichTextCustomEmoji{CustomEmojiID: "123", AlternativeText: "!"}, } if _, err := BuildHTML(P(Button(tgapi.RichMessageButton{Text: label, Disabled: &tgapi.DisabledButton{}}))); err != nil { t.Fatal(err) } for _, n := range []int{0, 1, 256, 257} { _, err := BuildHTML(P(Button(tgapi.RichMessageButton{Text: Text("Copy"), CopyText: &tgapi.CopyTextButton{Text: strings.Repeat("я", n)}}))) if (err == nil) != (n >= 1 && n <= 256) { t.Fatalf("copy length %d: %v", n, err) } } for _, text := range []tgapi.RichText{ Bold(Text(strings.Repeat("я", maxRichTextChars+1))), URL(Text(strings.Repeat("я", maxRichTextChars+1)), "https://example.com"), } { if _, err := BuildHTML(P(text)); !errors.Is(err, ErrRichTextTooLong) { t.Fatalf("nested text bypassed limit: %v", err) } } }