posts_test.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. package main
  2. import (
  3. "strings"
  4. "testing"
  5. "time"
  6. )
  7. func TestParsePost(t *testing.T) {
  8. mod := time.Date(2026, 3, 4, 15, 30, 0, 0, time.UTC)
  9. tests := []struct {
  10. name, file, src string
  11. slug, title string
  12. date string
  13. summary string
  14. draft bool
  15. wantErr string
  16. }{
  17. {
  18. name: "full front matter",
  19. file: "2026-01-01-first.md",
  20. src: "---\ntitle: \"Front title\"\ndate: 2026-02-02\nsummary: Short.\ndraft: false\n---\nBody\n",
  21. slug: "first", title: "Front title", date: "2026-02-02", summary: "Short.",
  22. },
  23. {
  24. name: "title from heading, date from filename",
  25. file: "2026-01-01-first.md",
  26. src: "\n# Heading title\n\nBody\n",
  27. slug: "first", title: "Heading title", date: "2026-01-01",
  28. },
  29. {
  30. name: "plain filename falls back to slug and mtime",
  31. file: "about.md",
  32. src: "Just text.\n",
  33. slug: "about", title: "about", date: "2026-03-04",
  34. },
  35. {
  36. name: "CRLF line endings",
  37. file: "crlf.md",
  38. src: "---\r\ntitle: Windows\r\ndate: 2026-05-05\r\n---\r\nBody\r\n",
  39. slug: "crlf", title: "Windows", date: "2026-05-05",
  40. },
  41. {
  42. name: "draft",
  43. file: "2026-01-01-wip.md",
  44. src: "---\ndraft: true\n---\n# WIP\n",
  45. slug: "wip", title: "WIP", date: "2026-01-01", draft: true,
  46. },
  47. {name: "unterminated front matter", file: "a.md", src: "---\ntitle: x\n", wantErr: "closing ---"},
  48. {name: "bad front matter line", file: "a.md", src: "---\nnot a pair\n---\n", wantErr: "expected key: value"},
  49. {name: "bad date", file: "a.md", src: "---\ndate: yesterday\n---\n", wantErr: "not YYYY-MM-DD"},
  50. {name: "bad draft", file: "a.md", src: "---\ndraft: maybe\n---\n", wantErr: "not true or false"},
  51. {name: "bad filename", file: "Hello World.md", src: "x", wantErr: "filename must be"},
  52. {name: "bad filename date", file: "2026-13-01-x.md", src: "x", wantErr: "bad date"},
  53. }
  54. md := newMarkdown()
  55. for _, tt := range tests {
  56. t.Run(tt.name, func(t *testing.T) {
  57. p, err := parsePost(md, tt.file, []byte(tt.src), mod)
  58. if tt.wantErr != "" {
  59. if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
  60. t.Fatalf("err = %v, want containing %q", err, tt.wantErr)
  61. }
  62. return
  63. }
  64. if err != nil {
  65. t.Fatal(err)
  66. }
  67. if p.Slug != tt.slug || p.Title != tt.title || p.Summary != tt.summary || p.Draft != tt.draft {
  68. t.Errorf("got slug=%q title=%q summary=%q draft=%v", p.Slug, p.Title, p.Summary, p.Draft)
  69. }
  70. if got := p.Date.Format(time.DateOnly); got != tt.date {
  71. t.Errorf("date = %s, want %s", got, tt.date)
  72. }
  73. })
  74. }
  75. }
  76. func TestHeadingTitleRemovedFromBody(t *testing.T) {
  77. p, err := parsePost(newMarkdown(), "x.md", []byte("# Title\n\nBody text\n"), time.Now())
  78. if err != nil {
  79. t.Fatal(err)
  80. }
  81. if strings.Contains(string(p.HTML), "<h1") {
  82. t.Errorf("heading used as title should not be repeated in body: %s", p.HTML)
  83. }
  84. }
  85. func TestMarkdownRendering(t *testing.T) {
  86. src := "| a | b |\n|---|---|\n| 1 | 2 |\n\n~~old~~ https://example.com\n\n<script>alert(1)</script>\n"
  87. p, err := parsePost(newMarkdown(), "x.md", []byte(src), time.Now())
  88. if err != nil {
  89. t.Fatal(err)
  90. }
  91. html := string(p.HTML)
  92. for _, want := range []string{"<table>", "<del>old</del>", `<a href="https://example.com">`} {
  93. if !strings.Contains(html, want) {
  94. t.Errorf("missing %q in %s", want, html)
  95. }
  96. }
  97. if strings.Contains(html, "<script") {
  98. t.Errorf("raw HTML was rendered: %s", html)
  99. }
  100. }