Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion lib/ui/html_widgets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"html/template"
"strings"
"sync"
"testing"

"github.com/linkdata/jaws"
Expand Down Expand Up @@ -72,11 +73,32 @@ func TestImg_RenderAndUpdate(t *testing.T) {
src := newTestSetter("image.png")
ui := NewImg(src)
elem, got := renderUI(t, rq, ui, "hidden")
mustMatch(t, `^<img id="Jid\.[0-9]+" hidden src="image\.png">$`, got)
mustMatch(t, `^<img id="Jid\.[0-9]+" src="image\.png" hidden>$`, got)
src.Set("image2.jpg")
ui.JawsUpdate(elem)
}

func TestImg_RenderGetterSrcTakesPrecedence(t *testing.T) {
_, rq := newCoreRequest(t)
var mu sync.Mutex
src := "getter.png"
getter := bind.New(&mu, &src).InitialHTMLAttr(func(bind.Binder[string], *jaws.Element) (attr template.HTMLAttr) {
attr = `src="hook.png" data-getter="yes"`
return
})

_, got := renderUI(t, rq, NewImg(getter), template.HTMLAttr(`src="caller.png" data-caller="yes"`))
canonical := strings.Index(got, `src="getter.png"`)
caller := strings.Index(got, `src="caller.png"`)
hook := strings.Index(got, `src="hook.png"`)
if canonical < 0 || caller < 0 || hook < 0 || caller < canonical || hook < canonical {
t.Fatalf("getter src does not take precedence: %s", got)
}
if !strings.Contains(got, `data-caller="yes"`) || !strings.Contains(got, `data-getter="yes"`) {
t.Fatalf("rendered image missing non-src attributes: %s", got)
}
}

func TestImg_RenderEscapesSrcAttr(t *testing.T) {
_, rq := newCoreRequest(t)
src := newTestSetter(`image"&copy=<x>'\` + "\n")
Expand Down
8 changes: 7 additions & 1 deletion lib/ui/img.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ui

import (
"io"
"slices"

"github.com/linkdata/jaws"
"github.com/linkdata/jaws/lib/bind"
Expand All @@ -10,6 +11,9 @@ import (

// Img renders an HTML img element whose src is read from a string getter.
//
// The getter-derived src takes precedence over any src attribute passed as a
// render param or returned by the getter's [jaws.InitialHTMLAttrHandler].
//
// One Img value may back multiple live [jaws.Element] values. Its getter is
// shared by those Elements and must be safe for their render, update and event
// calls.
Expand All @@ -23,7 +27,9 @@ func (u *Img) JawsRender(elem *jaws.Element, w io.Writer, params []any) (err err
_, getterAttrs := elem.ApplyGetter(u.Getter)
srcAttr := htmlio.Attr("src", u.JawsGet(elem))
attrs := append(elem.ApplyParams(params), getterAttrs...)
attrs = append(attrs, srcAttr)
// HTML parsing keeps the first duplicate attribute, so emit the canonical
// src before caller and getter attributes.
attrs = slices.Insert(attrs, 0, srcAttr)
err = htmlio.WriteHTMLInner(w, elem.Jid(), "img", "", "", attrs...)
return
}
Expand Down
Loading