fix: image alt on HTML-only translations and on markdown that starts with a tag
Blank content means content_html is the body (no 422 for content_html-only translations, a regression from the markdown alt fix); any other content is handled as markdown, so a body opening with a tag gets its inline image label and keeps its code samples. quick-261104-xae, review WR-02.
This commit is contained in:
@@ -723,6 +723,86 @@ func TestSetImageAlt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestSetImageAltBodyShapes pins the body rule of images/alt
|
||||
// (33-REVIEW-GAPFIX WR-02): a locale whose content is blank (a translation
|
||||
// that carries only content_html) gets the alt in content_html alone, and
|
||||
// any other content is read as markdown, also when it starts with a tag.
|
||||
func TestSetImageAltBodyShapes(t *testing.T) {
|
||||
env := newPostsEnv(t)
|
||||
editor := env.admin(t, controllers.PermissionAccessPosts, controllers.PermissionAccessOtherPosts)
|
||||
const a = "https://cdn.example.test/a.jpg"
|
||||
path := journalAPIPrefix + "/images/alt"
|
||||
decode := func(rec *httptest.ResponseRecorder) []map[string]any {
|
||||
var out struct {
|
||||
Changes []map[string]any `json:"changes"`
|
||||
}
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil {
|
||||
t.Fatalf("alt body %s: %v", rec.Body.String(), err)
|
||||
}
|
||||
return out.Changes
|
||||
}
|
||||
|
||||
t.Run("a translation that holds only content_html", func(t *testing.T) {
|
||||
post := apiPostID(t, env.expect(t, http.StatusCreated, http.MethodPost, journalAPIPrefix+"/posts", editor, map[string]any{
|
||||
"title": "HTML translation", "content": "Intro\n\n",
|
||||
"translations": map[string]any{"pl": map[string]any{"title": "Obrazy", "content_html": `<p><img src="` + a + `"></p>`}},
|
||||
}))
|
||||
changes := decode(env.expect(t, http.StatusOK, http.MethodPost, path, editor, map[string]any{
|
||||
"changes": []any{
|
||||
map[string]any{"post_id": post, "src": a, "alt": "Grzyb"},
|
||||
map[string]any{"post_id": post, "locale": "pl", "src": a, "alt": "Lew"},
|
||||
},
|
||||
}))
|
||||
if len(changes) != 2 || changes[1]["locale"] != "pl" || changes[1]["changed"] != true || changes[1]["old_alt"] != nil {
|
||||
t.Fatalf("answer = %#v", changes)
|
||||
}
|
||||
pl := translatedAttrs(t, env, post, "pl")
|
||||
if html, _ := pl["content_html"].(string); !strings.Contains(html, `alt="Lew"`) {
|
||||
t.Fatalf("pl = %#v", pl)
|
||||
}
|
||||
if c, has := pl["content"]; has {
|
||||
if s, _ := c.(string); strings.TrimSpace(s) != "" {
|
||||
t.Fatalf("pl content was written: %#v", pl)
|
||||
}
|
||||
}
|
||||
if got := storedPost(t, env, post).Content; got != "Intro\n\n" {
|
||||
t.Fatalf("default content = %q", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("markdown that starts with a tag", func(t *testing.T) {
|
||||
md := "<p class=\"lead\">Intro</p>\n\n\n\n```html\n<img src=\"" + a + "\">\n```\n"
|
||||
post := apiPostID(t, env.expect(t, http.StatusCreated, http.MethodPost, journalAPIPrefix+"/posts", editor, map[string]any{"title": "Tag lead", "content": md}))
|
||||
env.expect(t, http.StatusOK, http.MethodPost, path, editor, map[string]any{
|
||||
"changes": []any{map[string]any{"post_id": post, "src": a, "alt": "Grzyb"}},
|
||||
})
|
||||
want := "<p class=\"lead\">Intro</p>\n\n\n\n```html\n<img src=\"" + a + "\">\n```\n"
|
||||
stored := storedPost(t, env, post)
|
||||
if stored.Content != want || !strings.Contains(stored.ContentHTML, `alt="Grzyb"`) {
|
||||
t.Fatalf("after the alt = %q / %q", stored.Content, stored.ContentHTML)
|
||||
}
|
||||
env.expect(t, http.StatusOK, http.MethodPut, fmt.Sprintf("%s/posts/%d", journalAPIPrefix, post), editor, map[string]any{"content": stored.Content})
|
||||
if again := storedPost(t, env, post); !strings.Contains(again.ContentHTML, `alt="Grzyb"`) {
|
||||
t.Fatalf("the next content save lost the alt: %q", again.ContentHTML)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("markdown that starts with a tag and holds the image by reference", func(t *testing.T) {
|
||||
ref := "<p>Lead</p>\n\n![old][r]\n\n[r]: " + a + "\n"
|
||||
post := apiPostID(t, env.expect(t, http.StatusCreated, http.MethodPost, journalAPIPrefix+"/posts", editor, map[string]any{"title": "Tag lead reference", "content": ref}))
|
||||
before := storedPost(t, env, post)
|
||||
rec := env.expect(t, http.StatusUnprocessableEntity, http.MethodPost, path, editor, map[string]any{
|
||||
"changes": []any{map[string]any{"post_id": post, "src": a, "alt": "new"}},
|
||||
})
|
||||
if !strings.Contains(rec.Body.String(), "changes.0.src") || !strings.Contains(rec.Body.String(), "not an inline image") {
|
||||
t.Fatalf("reference answer %s", rec.Body.String())
|
||||
}
|
||||
if after := storedPost(t, env, post); after.Content != before.Content || after.ContentHTML != before.ContentHTML {
|
||||
t.Fatalf("a refused change wrote: %q / %q", after.Content, after.ContentHTML)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestBlogEditorRequestOptions (J1, UI-SPEC C28): the post and category
|
||||
// editor fields serve headingLevels and genericAnchors per form schema
|
||||
// request, following the H1 setting and the settings' generic link texts.
|
||||
|
||||
Reference in New Issue
Block a user