FILE / ScuroNeko/Laniakea
tgfmt/richtext/richblock_test.go
Исходный файл и его история в репозитории.
187 lines
4.6 KiB
Go
187 lines
4.6 KiB
Go
package richtext
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
)
|
|
|
|
func roundtripBlock(t *testing.T, in RichBlock) {
|
|
t.Helper()
|
|
b, err := json.Marshal(in)
|
|
if err != nil {
|
|
t.Fatalf("marshal: %v", err)
|
|
}
|
|
out, err := UnmarshalBlock(b)
|
|
if err != nil {
|
|
t.Fatalf("unmarshal %s: %v", b, err)
|
|
}
|
|
b2, err := json.Marshal(out)
|
|
if err != nil {
|
|
t.Fatalf("remarshal: %v", err)
|
|
}
|
|
if string(b) != string(b2) {
|
|
t.Fatalf("not stable:\n %s\n %s", b, b2)
|
|
}
|
|
}
|
|
|
|
func TestBlockRoundtrip(t *testing.T) {
|
|
cases := []RichBlock{
|
|
// wrap-блоки
|
|
Paragraph(String("Hello, world")),
|
|
SectionHeading(Bold(String("Chapter 1"))),
|
|
Footer(String("© 2024")),
|
|
Thinking(String("Let me reason step by step.")),
|
|
|
|
// preformatted
|
|
BlockPreformatted{Text: String("fmt.Println(\"hi\")"), Language: "go"},
|
|
BlockPreformatted{Text: String("no language")},
|
|
|
|
// цитаты
|
|
BlockBlockQuotation{Text: String("To be or not to be"), Caption: String("Shakespeare")},
|
|
BlockPullQuotation{Text: String("Pull me"), Caption: nil},
|
|
|
|
// список
|
|
BlockList{
|
|
Items: []RichBlockListItem{
|
|
{Blocks: []RichBlock{Paragraph(String("item 1"))}},
|
|
{Blocks: []RichBlock{Paragraph(String("item 2"))}},
|
|
},
|
|
Ordered: true,
|
|
},
|
|
BlockList{
|
|
Items: []RichBlockListItem{
|
|
{Blocks: []RichBlock{Paragraph(String("bullet"))}},
|
|
},
|
|
Ordered: false,
|
|
},
|
|
|
|
// коллаж и слайдшоу
|
|
BlockCollage{
|
|
Items: []RichBlock{BlockPhoto{FileID: "abc123"}},
|
|
Caption: String("A photo"),
|
|
},
|
|
BlockSlideshow{
|
|
Items: []RichBlock{BlockVideo{FileID: "vid1", Autoplay: true, Loop: false}},
|
|
Caption: nil,
|
|
},
|
|
|
|
// details
|
|
BlockDetails{
|
|
Title: String("Spoiler"),
|
|
Blocks: []RichBlock{Paragraph(String("Hidden content"))},
|
|
Open: false,
|
|
},
|
|
BlockDetails{
|
|
Title: Bold(String("Open details")),
|
|
Blocks: []RichBlock{BlockDivider{}, Paragraph(String("content"))},
|
|
Open: true,
|
|
},
|
|
|
|
// таблица
|
|
BlockTable{
|
|
Title: String("Results"),
|
|
Rows: [][]RichBlockTableCell{
|
|
{
|
|
{Content: []RichBlock{Paragraph(String("Cell A1"))}},
|
|
{Content: []RichBlock{Paragraph(String("Cell A2"))}, ColumnSpan: 2},
|
|
},
|
|
{
|
|
{Content: []RichBlock{Paragraph(String("Cell B1"))}, RowSpan: 2},
|
|
{Content: []RichBlock{Paragraph(String("Cell B2"))}},
|
|
},
|
|
},
|
|
Bordered: true,
|
|
Striped: false,
|
|
},
|
|
|
|
// карта
|
|
BlockMap{
|
|
Latitude: 55.7558, Longitude: 37.6173,
|
|
Zoom: 12, Width: 800, Height: 400,
|
|
Caption: String("Moscow"),
|
|
},
|
|
|
|
// медиа
|
|
BlockPhoto{FileID: "photo_file_id", Caption: String("A cat"), URL: "https://example.com/cat.jpg"},
|
|
BlockPhoto{FileID: "bare_photo"},
|
|
BlockVideo{FileID: "video_file_id", Caption: String("Demo"), Autoplay: true, Loop: true},
|
|
BlockAudio{FileID: "audio_file_id", Caption: String("Podcast ep. 1")},
|
|
BlockAnimation{FileID: "anim_file_id"},
|
|
BlockVoiceNote{FileID: "voice_file_id"},
|
|
|
|
// листья
|
|
BlockDivider{},
|
|
BlockMathematicalExpression{Expression: "E = mc^2"},
|
|
BlockAnchor{Name: "section-2"},
|
|
}
|
|
for _, c := range cases {
|
|
roundtripBlock(t, c)
|
|
}
|
|
}
|
|
|
|
func TestRichMessageRoundtrip(t *testing.T) {
|
|
msg := RichMessage{
|
|
Blocks: []RichBlock{
|
|
SectionHeading(String("Title")),
|
|
Paragraph(Array{String("Some "), Bold(String("bold")), String(" text")}),
|
|
BlockDivider{},
|
|
BlockList{
|
|
Items: []RichBlockListItem{
|
|
{Blocks: []RichBlock{Paragraph(String("First"))}},
|
|
{Blocks: []RichBlock{Paragraph(String("Second"))}},
|
|
},
|
|
Ordered: true,
|
|
},
|
|
BlockPhoto{FileID: "img1", Caption: String("Fig. 1")},
|
|
},
|
|
}
|
|
|
|
b, err := json.Marshal(msg)
|
|
if err != nil {
|
|
t.Fatalf("marshal: %v", err)
|
|
}
|
|
var out RichMessage
|
|
if err := json.Unmarshal(b, &out); err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
b2, err := json.Marshal(out)
|
|
if err != nil {
|
|
t.Fatalf("remarshal: %v", err)
|
|
}
|
|
if string(b) != string(b2) {
|
|
t.Fatalf("not stable:\n %s\n %s", b, b2)
|
|
}
|
|
}
|
|
|
|
func TestBlockDividerHasNoContent(t *testing.T) {
|
|
b, _ := json.Marshal(BlockDivider{})
|
|
var m map[string]any
|
|
_ = json.Unmarshal(b, &m)
|
|
if len(m) != 1 {
|
|
t.Fatalf("divider must only have type field: %s", b)
|
|
}
|
|
if m["type"] != "divider" {
|
|
t.Fatalf("unexpected type: %s", b)
|
|
}
|
|
}
|
|
|
|
func TestBlockUnknownTypeWithTextIsForwardCompat(t *testing.T) {
|
|
raw := []byte(`{"type":"future_tag","text":"hello"}`)
|
|
b, err := UnmarshalBlock(raw)
|
|
if err != nil {
|
|
t.Fatalf("forward-compat failed: %v", err)
|
|
}
|
|
w, ok := b.(BlockWrap)
|
|
if !ok || w.Tag != "future_tag" {
|
|
t.Fatalf("expected BlockWrap{future_tag}, got %T", b)
|
|
}
|
|
}
|
|
|
|
func TestBlockUnknownTypeWithoutTextIsError(t *testing.T) {
|
|
raw := []byte(`{"type":"mystery_leaf","value":42}`)
|
|
_, err := UnmarshalBlock(raw)
|
|
if err == nil {
|
|
t.Fatal("expected error for unknown type without text")
|
|
}
|
|
}
|