Skip to content

Commit 68004b1

Browse files
committed
handle duplicate elements in contents
1 parent 52f979e commit 68004b1

2 files changed

Lines changed: 81 additions & 21 deletions

File tree

ui/containerhelper.go

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -60,40 +60,42 @@ func (ui *ContainerHelper) RenderContainer(e *core.Element, w io.Writer, outerHT
6060
}
6161

6262
func (ui *ContainerHelper) UpdateContainer(e *core.Element) {
63-
var toRemove, toAppend []*core.Element
64-
var orderData []core.Jid
63+
var toAppend []*core.Element
6564

66-
oldMap := make(map[core.UI]*core.Element)
67-
newMap := make(map[core.UI]struct{})
68-
newContents := ui.Container.JawsContains(e)
69-
for _, childUI := range newContents {
70-
newMap[childUI] = struct{}{}
71-
}
65+
wantContents := ui.Container.JawsContains(e)
66+
newOrder := make([]core.Jid, 0, len(wantContents))
7267

7368
ui.mu.Lock()
69+
// build pool of reusable Elements keyed by UI, preserving duplicates
70+
pool := make(map[core.UI][]*core.Element, len(ui.contents))
7471
oldOrder := make([]core.Jid, len(ui.contents))
7572
for i, elem := range ui.contents {
7673
oldOrder[i] = elem.Jid()
77-
oldMap[elem.Ui()] = elem
78-
if _, ok := newMap[elem.Ui()]; !ok {
79-
toRemove = append(toRemove, elem)
80-
}
74+
pool[elem.Ui()] = append(pool[elem.Ui()], elem)
8175
}
76+
77+
// build new contents, reusing pooled Elements where possible
8278
ui.contents = ui.contents[:0]
83-
for _, childUI := range newContents {
84-
elem := oldMap[childUI]
85-
if elem == nil {
79+
for _, childUI := range wantContents {
80+
var elem *core.Element
81+
if elems := pool[childUI]; len(elems) > 0 {
82+
elem = elems[0]
83+
pool[childUI] = elems[1:]
84+
} else {
8685
elem = e.Request.NewElement(childUI)
8786
toAppend = append(toAppend, elem)
8887
}
8988
ui.contents = append(ui.contents, elem)
90-
orderData = append(orderData, elem.Jid())
89+
newOrder = append(newOrder, elem.Jid())
9190
}
9291
ui.mu.Unlock()
9392

94-
for _, elem := range toRemove {
95-
e.Remove(elem.Jid().String())
96-
e.Request.DeleteElement(elem)
93+
// remove leftover Elements not present in new contents
94+
for _, elems := range pool {
95+
for _, elem := range elems {
96+
e.Remove(elem.Jid().String())
97+
e.Request.DeleteElement(elem)
98+
}
9799
}
98100

99101
for _, elem := range toAppend {
@@ -102,7 +104,7 @@ func (ui *ContainerHelper) UpdateContainer(e *core.Element) {
102104
e.Append(template.HTML(sb.String())) // #nosec G203
103105
}
104106

105-
if !slices.Equal(oldOrder, orderData) {
106-
e.Order(orderData)
107+
if !slices.Equal(oldOrder, newOrder) {
108+
e.Order(newOrder)
107109
}
108110
}

ui/containerhelper_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"testing"
88

99
"github.com/linkdata/jaws/core"
10+
"github.com/linkdata/jaws/jid"
1011
"github.com/linkdata/jaws/what"
1112
)
1213

@@ -61,6 +62,63 @@ func TestContainerHelperUpdateContainer(t *testing.T) {
6162
}
6263
}
6364

65+
func TestContainerHelperUpdateContainerDuplicates(t *testing.T) {
66+
_, rq := newRequest(t)
67+
span1 := NewSpan(testHTMLGetter("span1"))
68+
span2 := NewSpan(testHTMLGetter("span2"))
69+
70+
// render with duplicate UI
71+
tc := &testContainer{contents: []core.UI{span1, span2, span1}}
72+
container := NewContainer("div", tc)
73+
elem, _ := renderUI(t, rq, container)
74+
75+
if len(container.contents) != 3 {
76+
t.Fatalf("want 3 contents got %d", len(container.contents))
77+
}
78+
// the two span1 Elements must have distinct Jids
79+
jid0 := container.contents[0].Jid()
80+
jid2 := container.contents[2].Jid()
81+
if jid0 == jid2 {
82+
t.Fatal("duplicate UI must produce distinct Jids")
83+
}
84+
85+
// remove one duplicate, keep the other
86+
tc.contents = []core.UI{span2, span1}
87+
container.JawsUpdate(elem)
88+
if len(container.contents) != 2 {
89+
t.Fatalf("want 2 contents got %d", len(container.contents))
90+
}
91+
// one of the two span1 Jids should have been removed
92+
kept := container.contents[1].Jid()
93+
if kept != jid0 && kept != jid2 {
94+
t.Fatalf("expected kept Jid to be one of the original span1 Jids")
95+
}
96+
var removedJid jid.Jid
97+
if kept == jid0 {
98+
removedJid = jid2
99+
} else {
100+
removedJid = jid0
101+
}
102+
if got := rq.GetElementByJid(removedJid); got != nil {
103+
t.Fatal("expected surplus duplicate to be deleted from request")
104+
}
105+
106+
// add more duplicates
107+
tc.contents = []core.UI{span1, span2, span1, span2}
108+
container.JawsUpdate(elem)
109+
if len(container.contents) != 4 {
110+
t.Fatalf("want 4 contents got %d", len(container.contents))
111+
}
112+
// all four must have distinct Jids
113+
jids := make(map[jid.Jid]struct{}, 4)
114+
for i, c := range container.contents {
115+
if _, ok := jids[c.Jid()]; ok {
116+
t.Fatalf("contents[%d] has duplicate Jid %v", i, c.Jid())
117+
}
118+
jids[c.Jid()] = struct{}{}
119+
}
120+
}
121+
64122
func TestContainerHelperRenderErrorPaths(t *testing.T) {
65123
_, rq := newRequest(t)
66124
renderErr := errors.New("render error")

0 commit comments

Comments
 (0)