Files
sm-journal-plugin/headings_test.go
Jakub Zych 305f94afd9 feat: H1 to H2 setting with the admin save conversion (tracer)
- content_h1_to_h2 and generic_anchors on the settings row (migration
  202610280001), Headings and links tab, config key
  golem15.journal.content_h1_to_h2 used when no settings row exists
- classes.ConvertH1 on the x/net/html tokenizer; the Blog Editor types
  convert while the setting is on and answer editor.h1_converted
- GET /posts/{slug} reports content_h1_to_h2 (additive key)
2026-11-04 09:44:58 +01:00

126 lines
4.3 KiB
Go

package journal_test
import (
"encoding/json"
"net/http"
"strings"
"testing"
"git.golem15.com/golem15/sm-journal-plugin/controllers"
"git.golem15.com/golem15/sm-journal-plugin/models"
)
const journalAPIPrefix = "/_journal/api/v1"
// setH1ToH2 switches the Journal H1 setting on the settings row.
func setH1ToH2(t *testing.T, env *adminEnv, on bool) {
t.Helper()
if err := env.gdb.Exec(`UPDATE golem15_journal_settings SET content_h1_to_h2 = ? WHERE id = 1`, on).Error; err != nil {
t.Fatal(err)
}
}
// publishPost publishes a post with a past published_at.
func publishPost(t *testing.T, env *adminEnv, id uint) {
t.Helper()
if err := env.gdb.Exec(`UPDATE golem15_journal_posts SET published = TRUE, published_at = NOW() - INTERVAL '1 hour' WHERE id = ?`, id).Error; err != nil {
t.Fatal(err)
}
}
// showH1Flag is GET /_journal/api/v1/posts/{slug}'s content_h1_to_h2.
func showH1Flag(t *testing.T, env *adminEnv, slug string) any {
t.Helper()
rec := env.expect(t, http.StatusOK, http.MethodGet, journalAPIPrefix+"/posts/"+slug, "", nil)
var body struct {
Data map[string]any `json:"data"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
t.Fatalf("show body %s: %v", rec.Body.String(), err)
}
flag, ok := body.Data["content_h1_to_h2"]
if !ok {
t.Fatalf("show answer has no content_h1_to_h2: %s", rec.Body.String())
}
return flag
}
// TestHeadingConversionTracer is the 33-02 tracer: an editor saves a post
// whose content holds an H1 through the admin and gets an H2 back with the
// h1_converted notice, and the public post answer reports the setting.
// With the setting off the same save keeps the H1, adds no notice, and the
// answer reports false.
func TestHeadingConversionTracer(t *testing.T) {
env := newPostsEnv(t)
token := env.admin(t,
controllers.PermissionAccessPosts,
controllers.PermissionAccessPublish,
controllers.PermissionAccessOtherPosts,
)
const submitted = "<h1>A</h1><p>x</p>"
save := func(t *testing.T, slug string) (uint, []adminNotice) {
t.Helper()
rec := env.expect(t, http.StatusCreated, http.MethodPost, adminAPI(adminPostsPath), token, map[string]any{
"title": map[string]any{"en": "Heading " + slug, "pl": "Nagłówek " + slug},
"slug": map[string]any{"en": slug, "pl": slug + "-pl"},
"content": map[string]any{"en": submitted, "pl": submitted},
})
return adminCreatedID(t, rec), adminNotices(t, rec)
}
t.Run("setting on", func(t *testing.T) {
setH1ToH2(t, env, true)
id, notices := save(t, "heading-on")
assertHostContent(t, env, id, "on", "<h2>A</h2><p>x</p>", "<h2>A</h2><p>x</p>")
pl := translatedAttrs(t, env, id, "pl")
if pl["content"] != "<h2>A</h2><p>x</p>" || pl["content_html"] != "<h2>A</h2><p>x</p>" {
t.Fatalf("pl content = %#v, content_html = %#v, want the H2", pl["content"], pl["content_html"])
}
for _, locale := range []string{"en", "pl"} {
found := false
for _, n := range notices {
if n.Field == "content" && n.Locale == locale &&
n.Message == "1 H1 heading was converted to H2, because the page shows the post title as the H1." {
found = true
}
}
if !found {
t.Fatalf("%s notices = %#v, want the h1_converted phrase with count 1", locale, notices)
}
}
publishPost(t, env, id)
if flag := showH1Flag(t, env, "heading-on"); flag != true {
t.Fatalf("content_h1_to_h2 = %#v, want true", flag)
}
})
t.Run("setting off", func(t *testing.T) {
setH1ToH2(t, env, false)
t.Cleanup(func() { setH1ToH2(t, env, true) })
id, notices := save(t, "heading-off")
assertHostContent(t, env, id, "off", submitted, submitted)
for _, n := range notices {
if strings.Contains(n.Message, "H1") {
t.Fatalf("setting off: notice %#v", n)
}
}
if notices != nil {
t.Fatalf("setting off: clean save carries meta.notices %#v", notices)
}
publishPost(t, env, id)
if flag := showH1Flag(t, env, "heading-off"); flag != false {
t.Fatalf("content_h1_to_h2 = %#v, want false", flag)
}
// D-50: switching the setting never rewrites stored content.
setH1ToH2(t, env, true)
var stored models.Post
if err := env.gdb.First(&stored, id).Error; err != nil {
t.Fatal(err)
}
if stored.Content != submitted || stored.ContentHTML != submitted {
t.Fatalf("switching the setting on rewrote content: %q / %q", stored.Content, stored.ContentHTML)
}
})
}