test: unit tests for heading conversion, image alt, content facts and related settings
- ConvertContentH1 and ConvertTranslationH1 for post bodies and category pillar text - SetImageAlt with several matches, repeated alt attributes and markup that only looks like an img - the content facts outline, images, decorative images and embeds - the related-posts mode and count
This commit is contained in:
69
classes/content_facts_test.go
Normal file
69
classes/content_facts_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package classes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/pact"
|
||||
)
|
||||
|
||||
// TestParseContentFacts pins what a body says about itself: images with
|
||||
// alt, decorative (alt present and empty) and size attributes (negative
|
||||
// and invalid sizes 0), an img without src skipped; YouTube embeds of the
|
||||
// editor's figure; the heading outline with collapsed text, inline tags
|
||||
// joined, block children spaced, a no-break space collapsed like any
|
||||
// other, a heading inside a heading read as part
|
||||
// of the outer one.
|
||||
func TestParseContentFacts(t *testing.T) {
|
||||
f := parseContentFacts(`<h2>Lwia <em>grzywa</em></h2>
|
||||
<p><img src=" /m/a.jpg " alt=" Lion's mane " width="800" height="-1"><img src="/m/line.png" alt=""><img alt="no src"><img src="/m/b.jpg" width="x"></p>
|
||||
<figure data-embed="youtube" data-id="abc123"></figure><figure data-embed="youtube"></figure><figure data-embed="vimeo" data-id="9"></figure>
|
||||
<h3>Dawkowanie<br>i forma</h3><h4><span>Outer <h5>inner</h5> tail</span></h4><h2></h2>`)
|
||||
wantImages := []pact.ContentImage{
|
||||
{URL: "/m/a.jpg", Alt: "Lion's mane", Width: 800},
|
||||
{URL: "/m/line.png", Decorative: true},
|
||||
{URL: "/m/b.jpg"},
|
||||
}
|
||||
if len(f.images) != len(wantImages) {
|
||||
t.Fatalf("images = %+v", f.images)
|
||||
}
|
||||
for i, w := range wantImages {
|
||||
if f.images[i] != w {
|
||||
t.Errorf("image %d = %+v, want %+v", i, f.images[i], w)
|
||||
}
|
||||
}
|
||||
if len(f.videos) != 1 || f.videos[0] != (pact.ContentVideo{Provider: "youtube", ID: "abc123"}) {
|
||||
t.Fatalf("videos = %+v", f.videos)
|
||||
}
|
||||
wantHeadings := []pact.ContentHeading{{Level: 2, Text: "Lwia grzywa"}, {Level: 3, Text: "Dawkowanie i forma"}, {Level: 4, Text: "Outer inner tail"}, {Level: 2, Text: ""}}
|
||||
if len(f.headings) != len(wantHeadings) {
|
||||
t.Fatalf("headings = %+v", f.headings)
|
||||
}
|
||||
for i, w := range wantHeadings {
|
||||
if f.headings[i] != w {
|
||||
t.Errorf("heading %d = %+v, want %+v", i, f.headings[i], w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseContentFactsEmpty pins an empty body and the category pillar
|
||||
// text read the same way (headings only).
|
||||
func TestParseContentFactsEmpty(t *testing.T) {
|
||||
if f := parseContentFacts(" "); f.images != nil || f.videos != nil || f.headings != nil {
|
||||
t.Fatalf("blank = %+v", f)
|
||||
}
|
||||
pillar := parseContentFacts("<p>Intro</p><h2>Gatunki</h2><h4>Skipped</h4>")
|
||||
if len(pillar.headings) != 2 || pillar.headings[1].Level != 4 {
|
||||
t.Fatalf("pillar = %+v", pillar.headings)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTopHeading pins the record's highest heading level: 2 while the
|
||||
// H1 setting turns body H1s into H2s, 0 (any) otherwise.
|
||||
func TestTopHeading(t *testing.T) {
|
||||
if topHeading(true) != 2 || topHeading(false) != 0 {
|
||||
t.Fatal("topHeading is wrong")
|
||||
}
|
||||
if toolUpdatePost != "journal_update_post" || toolFixHeadings != "journal_fix_headings" || toolSetImageAlt != "journal_set_image_alt" {
|
||||
t.Fatal("the descriptor's tool names changed")
|
||||
}
|
||||
}
|
||||
@@ -28,3 +28,41 @@ func TestConvertH1(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestConvertContentH1 pins the body pair: content_html's count when its
|
||||
// HTML had H1s, content's otherwise (a markdown body's H1 is only in its
|
||||
// rendered HTML), both converted.
|
||||
func TestConvertContentH1(t *testing.T) {
|
||||
c, h, n := ConvertContentH1("# Title\n\ntext", "<h1>Title</h1><p>text</p>")
|
||||
if c != "# Title\n\ntext" || h != "<h2>Title</h2><p>text</p>" || n != 1 {
|
||||
t.Fatalf("markdown body = %q %q %d", c, h, n)
|
||||
}
|
||||
c, h, n = ConvertContentH1("<h1>a</h1><h1>b</h1>", "")
|
||||
if c != "<h2>a</h2><h2>b</h2>" || h != "" || n != 2 {
|
||||
t.Fatalf("HTML body without rendered HTML = %q %q %d", c, h, n)
|
||||
}
|
||||
if _, _, n := ConvertContentH1("<p>x</p>", "<p>x</p>"); n != 0 {
|
||||
t.Fatal("no H1, nothing converted")
|
||||
}
|
||||
if CountH1(`<h1>a</h1><div><h1 class="x">b</h1></div><h2>c</h2>`) != 2 || CountH1("") != 0 {
|
||||
t.Fatal("CountH1 is wrong")
|
||||
}
|
||||
}
|
||||
|
||||
// TestConvertTranslationH1 pins a locale's translated attributes,
|
||||
// changed in place: content and content_html converted, other attributes
|
||||
// and values that are not strings left alone (a category's pillar text
|
||||
// takes the same path as a post body).
|
||||
func TestConvertTranslationH1(t *testing.T) {
|
||||
attrs := map[string]any{"title": "<h1>not content</h1>", "content": "<h1>Pillar</h1><p>x</p>", "content_html": 5}
|
||||
if n := ConvertTranslationH1(attrs); n != 1 || attrs["content"] != "<h2>Pillar</h2><p>x</p>" || attrs["title"] != "<h1>not content</h1>" || attrs["content_html"] != 5 {
|
||||
t.Fatalf("attrs = %v n=%d", attrs, n)
|
||||
}
|
||||
both := map[string]any{"content": "# T", "content_html": "<h1>T</h1>"}
|
||||
if n := ConvertTranslationH1(both); n != 1 || both["content_html"] != "<h2>T</h2>" || both["content"] != "# T" {
|
||||
t.Fatalf("both = %v n=%d", both, n)
|
||||
}
|
||||
if ConvertTranslationH1(nil) != 0 || ConvertTranslationH1(map[string]any{"title": "x"}) != 0 {
|
||||
t.Fatal("nothing to convert")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,3 +20,28 @@ func TestSetImageAlt(t *testing.T) {
|
||||
t.Fatalf("missing = %q, %d", out, n)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSetImageAltEdges pins the edges: every matching img is rewritten
|
||||
// and counted (the first one's old alt answered), repeated alt attributes
|
||||
// collapse to one, a src given escaped does not match the unescaped one,
|
||||
// an empty src and a body without img change nothing, and text that
|
||||
// looks like an img inside a comment or script is not touched.
|
||||
func TestSetImageAltEdges(t *testing.T) {
|
||||
doc := `<img src="a.jpg" alt="one"><p>t</p><img alt="x" src="a.jpg" alt="y"><!-- <img src="a.jpg"> --><script>"<img src=a.jpg>"</script>`
|
||||
out, n, old := SetImageAlt(doc, "a.jpg", "new")
|
||||
if n != 2 || old == nil || *old != "one" {
|
||||
t.Fatalf("n=%d old=%v", n, old)
|
||||
}
|
||||
want := `<img src="a.jpg" alt="new"><p>t</p><img alt="new" src="a.jpg"><!-- <img src="a.jpg"> --><script>"<img src=a.jpg>"</script>`
|
||||
if out != want {
|
||||
t.Fatalf("out = %q\nwant %q", out, want)
|
||||
}
|
||||
if out, n, _ := SetImageAlt(`<img src="a.jpg?x=1&y=2">`, "a.jpg?x=1&y=2", "z"); n != 0 || out != `<img src="a.jpg?x=1&y=2">` {
|
||||
t.Fatalf("escaped src matched: %q %d", out, n)
|
||||
}
|
||||
for _, tc := range []struct{ html, src string }{{`<img src="a.jpg">`, ""}, {`<p>no images</p>`, "a.jpg"}, {"", "a.jpg"}} {
|
||||
if out, n, old := SetImageAlt(tc.html, tc.src, "x"); out != tc.html || n != 0 || old != nil {
|
||||
t.Errorf("SetImageAlt(%q, %q) = %q %d %v", tc.html, tc.src, out, n, old)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
47
classes/related_test.go
Normal file
47
classes/related_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package classes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/compass"
|
||||
)
|
||||
|
||||
// TestReadRelatedSettings pins the related-posts config: the category mode
|
||||
// and 3 by default; "scored" in any case turns scoring on; the count
|
||||
// clamped to 3..6, 0 or a non-number falling back to 3. The scoring and
|
||||
// its ties by the newest post are TestJournalAPIMatrix's on the database.
|
||||
func TestReadRelatedSettings(t *testing.T) {
|
||||
if s := ReadRelatedSettings(nil); s.Mode != RelatedModeCategory || s.Count != 3 || s.Scored() {
|
||||
t.Fatalf("nil config = %+v", s)
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
mode string
|
||||
count any
|
||||
want RelatedSettings
|
||||
}{
|
||||
{"", nil, RelatedSettings{RelatedModeCategory, 3}},
|
||||
{" Scored ", 5, RelatedSettings{RelatedModeScored, 5}},
|
||||
{"scored", 1, RelatedSettings{RelatedModeScored, 3}},
|
||||
{"scored", 99, RelatedSettings{RelatedModeScored, 6}},
|
||||
{"category", 4, RelatedSettings{RelatedModeCategory, 4}},
|
||||
{"tags", 0, RelatedSettings{RelatedModeCategory, 3}},
|
||||
{"scored", "many", RelatedSettings{RelatedModeScored, 3}},
|
||||
} {
|
||||
cfg, err := compass.Open(compass.Options{Dir: t.TempDir()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tc.mode != "" {
|
||||
_ = cfg.Set(RelatedModeKey, tc.mode)
|
||||
}
|
||||
if tc.count != nil {
|
||||
_ = cfg.Set(RelatedCountKey, tc.count)
|
||||
}
|
||||
if got := ReadRelatedSettings(cfg); got != tc.want {
|
||||
t.Errorf("mode %q count %v = %+v, want %+v", tc.mode, tc.count, got, tc.want)
|
||||
}
|
||||
}
|
||||
if relatedTagPoints != 2 || relatedCategoryPoints != 1 {
|
||||
t.Fatal("a shared tag is 2 points, a shared category 1")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user