Fix splitByIndex diff

This commit is contained in:
Paul-Henri Froidmont 2025-09-25 03:37:36 +02:00
parent 681feced9f
commit eab5eb3316
Signed by: phfroidmont
GPG key ID: BE948AFD7E7873BE
4 changed files with 65 additions and 21 deletions

View file

@ -41,19 +41,18 @@ object DiffBuilder:
case Content.DynElementColl(dyn) => ??? case Content.DynElementColl(dyn) => ???
case Content.DynSplit(splitVar) => case Content.DynSplit(splitVar) =>
splitVar.render(trackUpdates) match splitVar.render(trackUpdates) match
case Some((entries, keysCount)) => case Some((entries, keysCount, includeStatics)) =>
val static = val static =
entries.collectFirst { case (_, Some(el)) => el.static }.getOrElse(List.empty) if !trackUpdates || includeStatics then
val includeStatic = !trackUpdates || keysCount == entries.count(_._2.isDefined) entries.collectFirst { case (_, el) => el.static }.getOrElse(List.empty)
else List.empty
List( List(
Some( Some(
Diff.Comprehension( Diff.Comprehension(
static = if includeStatic then static else Seq.empty, static = static,
entries = entries.map { entries = entries.map((key, el) =>
case (key, Some(el)) => Diff.Dynamic(key.toString, build(Seq.empty, el.dynamicMods, trackUpdates))
Diff.Dynamic(key.toString, build(Seq.empty, el.dynamicMods, trackUpdates)) ),
case (key, None) => Diff.Dynamic(key.toString, Diff.Deleted)
},
count = keysCount count = keysCount
) )
) )

View file

@ -99,10 +99,12 @@ private class SplitVar[I, O, Key](
private val memoized: mutable.Map[Key, (Var[I], O)] = private val memoized: mutable.Map[Key, (Var[I], O)] =
mutable.Map.empty mutable.Map.empty
private var nonEmptySyncCount = 0
private[scalive] def sync(): Unit = private[scalive] def sync(): Unit =
parent.sync() parent.sync()
if parent.changed then if parent.changed then
// We keep track of the key to set deleted ones to None // We keep track of the keys to remove deleted ones afterwards
val nextKeys = mutable.HashSet.empty[Key] val nextKeys = mutable.HashSet.empty[Key]
parent.currentValue.foreach(input => parent.currentValue.foreach(input =>
val entryKey = key(input) val entryKey = key(input)
@ -122,16 +124,19 @@ private class SplitVar[I, O, Key](
if !nextKeys.contains(k) then if !nextKeys.contains(k) then
val _ = memoized.remove(k) val _ = memoized.remove(k)
) )
if memoized.nonEmpty then nonEmptySyncCount += 1
private[scalive] def render(trackUpdates: Boolean) private[scalive] def render(trackUpdates: Boolean)
: Option[(changeList: List[(Key, Option[O])], keysCount: Int)] = : Option[(changeList: List[(Int, O)], keysCount: Int, includeStatics: Boolean)] =
if parent.changed || !trackUpdates then if parent.changed || !trackUpdates then
Some( Some(
( (
memoized.collect { changeList = memoized.values.zipWithIndex.collect {
case (k, (entryVar, output)) if !trackUpdates || entryVar.changed => (k, Some(output)) case ((entryVar, output), index) if !trackUpdates || entryVar.changed =>
(index, output)
}.toList, }.toList,
memoized.size keysCount = memoized.size,
includeStatics = nonEmptySyncCount == 1
) )
) )
else None else None

View file

@ -33,13 +33,11 @@ object HtmlBuilder:
dyn.render(false).foreach(_.foreach(el => build(el.static, el.dynamicMods, strw))) dyn.render(false).foreach(_.foreach(el => build(el.static, el.dynamicMods, strw)))
case Content.DynElementColl(dyn) => ??? case Content.DynElementColl(dyn) => ???
case Content.DynSplit(splitVar) => case Content.DynSplit(splitVar) =>
val (entries, _) = splitVar.render(false).getOrElse(List.empty -> 0) val (entries, _, _) = splitVar.render(false).getOrElse((List.empty, 0, true))
val staticOpt = entries.collectFirst { case (_, Some(el)) => el.static } val staticOpt = entries.collectFirst { case (_, el) => el.static }
entries.foreach { entries.foreach((_, entryEl) =>
case (_, Some(entryEl)) => build(staticOpt.getOrElse(Nil), entryEl.dynamicMods, strw)
build(staticOpt.getOrElse(Nil), entryEl.dynamicMods, strw) )
case _ => ()
}
strw.write(static.last) strw.write(static.last)
end HtmlBuilder end HtmlBuilder

View file

@ -290,6 +290,48 @@ object LiveViewSpec extends TestSuite:
) )
) )
} }
test("diff add one to empty list") {
val model = Var(TestModel(items = List.empty))
val el =
div(
ul(
model(_.items).splitByIndex((_, elem) =>
li(
"Nom: ",
elem(_.name),
" Age: ",
elem(_.age.toString)
)
)
)
)
el.syncAll()
el.setAllUnchanged()
model.update(_.copy(items = List(NestedModel("a", 20))))
assertEqualsDiff(
el,
Json.Obj(
"0" ->
Json
.Obj(
"s" -> Json.Arr(
Json.Str("<li>Nom: "),
Json.Str(" Age: "),
Json.Str("</li>")
),
"k" -> Json.Obj(
"0" -> Json.Obj(
"0" -> Json.Str("a"),
"1" -> Json.Str("20")
),
"kc" -> Json.Num(1)
)
)
)
)
}
test("diff with first item removed") { test("diff with first item removed") {
model.update(_.copy(items = initModel.items.tail)) model.update(_.copy(items = initModel.items.tail))
assertEqualsDiff( assertEqualsDiff(