mirror of
https://github.com/phfroidmont/scalive.git
synced 2025-12-25 13:36:59 +01:00
Rework diff aware state management allowing an API similar to Laminar
This commit is contained in:
parent
8f19ccfbb4
commit
cff02a4c96
17 changed files with 857 additions and 710 deletions
|
|
@ -8,326 +8,352 @@ object LiveViewSpec extends TestSuite:
|
|||
|
||||
final case class TestModel(
|
||||
title: String = "title value",
|
||||
otherString: String = "other string value",
|
||||
bool: Boolean = false,
|
||||
nestedTitle: String = "nested title value",
|
||||
cls: String = "text-sm",
|
||||
items: List[NestedModel] = List.empty)
|
||||
final case class NestedModel(name: String, age: Int)
|
||||
final case class UpdateCmd(f: TestModel => TestModel)
|
||||
|
||||
// def assertEqualsJson(actual: Diff, expected: Json) =
|
||||
// assert(actual.toJsonPretty == expected.toJsonPretty)
|
||||
def assertEqualsDiff(el: HtmlElement, expected: Json, trackChanges: Boolean = true) =
|
||||
el.syncAll()
|
||||
val actual = DiffBuilder.build(el, trackUpdates = trackChanges)
|
||||
assert(actual.toJsonPretty == expected.toJsonPretty)
|
||||
|
||||
val emptyDiff = Json.Obj.empty
|
||||
|
||||
val tests = Tests {
|
||||
|
||||
// test("Static only") {
|
||||
// val lv =
|
||||
// LiveView(
|
||||
// new View:
|
||||
// type Model = Unit
|
||||
// val render = div("Static string")
|
||||
// ,
|
||||
// ()
|
||||
// )
|
||||
// test("init") {
|
||||
// assertEqualsJson(
|
||||
// lv.fullDiff,
|
||||
// Json.Obj(
|
||||
// "s" -> Json.Arr(Json.Str("<div>Static string</div>"))
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// test("diff") {
|
||||
// assertEqualsJson(lv.diff, emptyDiff)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// test("Dynamic string") {
|
||||
// val lv =
|
||||
// LiveView(
|
||||
// new View:
|
||||
// type Model = TestModel
|
||||
// val render =
|
||||
// div(model(_.title))
|
||||
// ,
|
||||
// TestModel()
|
||||
// )
|
||||
// test("init") {
|
||||
// assertEqualsJson(
|
||||
// lv.fullDiff,
|
||||
// Json
|
||||
// .Obj(
|
||||
// "s" -> Json.Arr(Json.Str("<div>"), Json.Str("</div>")),
|
||||
// "0" -> Json.Str("title value")
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// test("diff no update") {
|
||||
// assertEqualsJson(lv.diff, emptyDiff)
|
||||
// }
|
||||
// test("diff with update") {
|
||||
// lv.update(TestModel(title = "title updated"))
|
||||
// assertEqualsJson(
|
||||
// lv.diff,
|
||||
// Json.Obj("0" -> Json.Str("title updated"))
|
||||
// )
|
||||
// }
|
||||
// test("diff with update and no change") {
|
||||
// lv.update(TestModel(title = "title updated"))
|
||||
// lv.update(TestModel(title = "title updated"))
|
||||
// assertEqualsJson(lv.diff, emptyDiff)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// test("Dynamic attribute") {
|
||||
// val lv =
|
||||
// LiveView(
|
||||
// new View:
|
||||
// type Model = TestModel
|
||||
// val render =
|
||||
// div(cls := model(_.cls))
|
||||
// ,
|
||||
// TestModel()
|
||||
// )
|
||||
// test("init") {
|
||||
// assertEqualsJson(
|
||||
// lv.fullDiff,
|
||||
// Json
|
||||
// .Obj(
|
||||
// "s" -> Json
|
||||
// .Arr(Json.Str("<div class=\""), Json.Str("\"></div>")),
|
||||
// "0" -> Json.Str("text-sm")
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// test("diff no update") {
|
||||
// assertEqualsJson(lv.diff, emptyDiff)
|
||||
// }
|
||||
// test("diff with update") {
|
||||
// lv.update(TestModel(cls = "text-md"))
|
||||
// assertEqualsJson(
|
||||
// lv.diff,
|
||||
// Json.Obj("0" -> Json.Str("text-md"))
|
||||
// )
|
||||
// }
|
||||
// test("diff with update and no change") {
|
||||
// lv.update(TestModel(cls = "text-md"))
|
||||
// lv.update(TestModel(cls = "text-md"))
|
||||
// assertEqualsJson(lv.diff, emptyDiff)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// test("when mod") {
|
||||
// val lv =
|
||||
// LiveView(
|
||||
// new View:
|
||||
// type Model = TestModel
|
||||
// val render =
|
||||
// div(
|
||||
// model.when(_.bool)(
|
||||
// div("static string", model(_.nestedTitle))
|
||||
// )
|
||||
// )
|
||||
// ,
|
||||
// TestModel()
|
||||
// )
|
||||
// test("init") {
|
||||
// assertEqualsJson(
|
||||
// lv.fullDiff,
|
||||
// Json
|
||||
// .Obj(
|
||||
// "s" -> Json.Arr(Json.Str("<div>"), Json.Str("</div>")),
|
||||
// "0" -> Json.Bool(false)
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// test("diff no update") {
|
||||
// assertEqualsJson(lv.diff, emptyDiff)
|
||||
// }
|
||||
// test("diff with unrelated update") {
|
||||
// lv.update(TestModel(title = "title updated"))
|
||||
// assertEqualsJson(lv.diff, emptyDiff)
|
||||
// }
|
||||
// test("diff when true") {
|
||||
// lv.update(TestModel(bool = true))
|
||||
// assertEqualsJson(
|
||||
// lv.diff,
|
||||
// Json.Obj(
|
||||
// "0" ->
|
||||
// Json
|
||||
// .Obj(
|
||||
// "s" -> Json
|
||||
// .Arr(Json.Str("<div>static string"), Json.Str("</div>")),
|
||||
// "0" -> Json.Str("nested title value")
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// test("diff when nested change") {
|
||||
// lv.update(TestModel(bool = true))
|
||||
// lv.update(TestModel(bool = true, nestedTitle = "nested title updated"))
|
||||
// assertEqualsJson(
|
||||
// lv.diff,
|
||||
// Json.Obj(
|
||||
// "0" ->
|
||||
// Json
|
||||
// .Obj(
|
||||
// "0" -> Json.Str("nested title updated")
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// test("splitByIndex mod") {
|
||||
// val initModel =
|
||||
// TestModel(
|
||||
// items = List(
|
||||
// NestedModel("a", 10),
|
||||
// NestedModel("b", 15),
|
||||
// NestedModel("c", 20)
|
||||
// )
|
||||
// )
|
||||
// val lv =
|
||||
// LiveView(
|
||||
// new View:
|
||||
// type Model = TestModel
|
||||
// val render =
|
||||
// div(
|
||||
// ul(
|
||||
// model.splitByIndex(_.items)(elem =>
|
||||
// li(
|
||||
// "Nom: ",
|
||||
// elem(_.name),
|
||||
// " Age: ",
|
||||
// elem(_.age.toString)
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// ,
|
||||
// initModel
|
||||
// )
|
||||
// test("init") {
|
||||
// assertEqualsJson(
|
||||
// lv.fullDiff,
|
||||
// Json
|
||||
// .Obj(
|
||||
// "s" -> Json.Arr(Json.Str("<div><ul>"), Json.Str("</ul></div>")),
|
||||
// "0" -> Json.Obj(
|
||||
// "s" -> Json.Arr(
|
||||
// Json.Str("<li>Nom: "),
|
||||
// Json.Str(" Age: "),
|
||||
// Json.Str("</li>")
|
||||
// ),
|
||||
// "d" -> Json.Obj(
|
||||
// "0" -> Json.Obj(
|
||||
// "0" -> Json.Str("a"),
|
||||
// "1" -> Json.Str("10")
|
||||
// ),
|
||||
// "1" -> Json.Obj(
|
||||
// "0" -> Json.Str("b"),
|
||||
// "1" -> Json.Str("15")
|
||||
// ),
|
||||
// "2" -> Json.Obj(
|
||||
// "0" -> Json.Str("c"),
|
||||
// "1" -> Json.Str("20")
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// test("diff no update") {
|
||||
// assertEqualsJson(lv.diff, emptyDiff)
|
||||
// }
|
||||
// test("diff with unrelated update") {
|
||||
// lv.update(initModel.copy(title = "title updated"))
|
||||
// assertEqualsJson(lv.diff, emptyDiff)
|
||||
// }
|
||||
// test("diff with item changed") {
|
||||
// lv.update(
|
||||
// initModel.copy(items = initModel.items.updated(2, NestedModel("c", 99)))
|
||||
// )
|
||||
// assertEqualsJson(
|
||||
// lv.diff,
|
||||
// Json.Obj(
|
||||
// "0" ->
|
||||
// Json
|
||||
// .Obj(
|
||||
// "d" -> Json.Obj(
|
||||
// "2" -> Json.Obj(
|
||||
// "1" -> Json.Str("99")
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// test("diff with item added") {
|
||||
// lv.update(
|
||||
// initModel.copy(items = initModel.items.appended(NestedModel("d", 35)))
|
||||
// )
|
||||
// assertEqualsJson(
|
||||
// lv.diff,
|
||||
// Json.Obj(
|
||||
// "0" ->
|
||||
// Json
|
||||
// .Obj(
|
||||
// "d" -> Json.Obj(
|
||||
// "3" -> Json.Obj(
|
||||
// "0" -> Json.Str("d"),
|
||||
// "1" -> Json.Str("35")
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// test("diff with first item removed") {
|
||||
// lv.update(
|
||||
// initModel.copy(items = initModel.items.tail)
|
||||
// )
|
||||
// assertEqualsJson(
|
||||
// lv.diff,
|
||||
// Json.Obj(
|
||||
// "0" ->
|
||||
// Json
|
||||
// .Obj(
|
||||
// "d" -> Json.Obj(
|
||||
// "0" -> Json.Obj(
|
||||
// "0" -> Json.Str("b"),
|
||||
// "1" -> Json.Str("15")
|
||||
// ),
|
||||
// "1" -> Json.Obj(
|
||||
// "0" -> Json.Str("c"),
|
||||
// "1" -> Json.Str("20")
|
||||
// ),
|
||||
// "2" -> Json.Bool(false)
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
// test("diff all removed") {
|
||||
// lv.update(initModel.copy(items = List.empty))
|
||||
// assertEqualsJson(
|
||||
// lv.diff,
|
||||
// Json.Obj(
|
||||
// "0" ->
|
||||
// Json
|
||||
// .Obj(
|
||||
// "d" -> Json.Obj(
|
||||
// "0" -> Json.Bool(false),
|
||||
// "1" -> Json.Bool(false),
|
||||
// "2" -> Json.Bool(false)
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
// )
|
||||
//
|
||||
// }
|
||||
// }
|
||||
test("Static only") {
|
||||
val lv =
|
||||
new LiveView[Unit]:
|
||||
val el = div("Static string")
|
||||
def handleCommand(cmd: Unit): Unit = ()
|
||||
lv.el.syncAll()
|
||||
|
||||
test("init") {
|
||||
assertEqualsDiff(
|
||||
lv.el,
|
||||
Json.Obj(
|
||||
"s" -> Json.Arr(Json.Str("<div>Static string</div>"))
|
||||
),
|
||||
trackChanges = false
|
||||
)
|
||||
}
|
||||
test("diff") {
|
||||
assertEqualsDiff(lv.el, emptyDiff)
|
||||
}
|
||||
}
|
||||
|
||||
test("Dynamic string") {
|
||||
val lv =
|
||||
new LiveView[UpdateCmd]:
|
||||
val model = Var(TestModel())
|
||||
val el =
|
||||
div(
|
||||
h1(model(_.title)),
|
||||
p(model(_.otherString))
|
||||
)
|
||||
def handleCommand(cmd: UpdateCmd): Unit = model.update(cmd.f)
|
||||
|
||||
lv.el.syncAll()
|
||||
lv.el.setAllUnchanged()
|
||||
|
||||
test("init") {
|
||||
assertEqualsDiff(
|
||||
lv.el,
|
||||
Json
|
||||
.Obj(
|
||||
"s" -> Json.Arr(Json.Str("<div><h1>"), Json.Str("</h1><p>"), Json.Str("</p></div>")),
|
||||
"0" -> Json.Str("title value"),
|
||||
"1" -> Json.Str("other string value")
|
||||
),
|
||||
trackChanges = false
|
||||
)
|
||||
}
|
||||
test("diff no update") {
|
||||
assertEqualsDiff(lv.el, emptyDiff)
|
||||
}
|
||||
test("diff with update") {
|
||||
lv.handleCommand(UpdateCmd(_.copy(title = "title updated")))
|
||||
assertEqualsDiff(
|
||||
lv.el,
|
||||
Json.Obj("0" -> Json.Str("title updated"))
|
||||
)
|
||||
}
|
||||
test("diff with update and no change") {
|
||||
lv.handleCommand(UpdateCmd(_.copy(title = "title value")))
|
||||
assertEqualsDiff(lv.el, emptyDiff)
|
||||
}
|
||||
test("diff with update in multiple commands") {
|
||||
lv.handleCommand(UpdateCmd(_.copy(title = "title updated")))
|
||||
lv.handleCommand(UpdateCmd(_.copy(otherString = "other string updated")))
|
||||
assertEqualsDiff(
|
||||
lv.el,
|
||||
Json
|
||||
.Obj(
|
||||
"0" -> Json.Str("title updated"),
|
||||
"1" -> Json.Str("other string updated")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
test("Dynamic attribute") {
|
||||
val lv =
|
||||
new LiveView[UpdateCmd]:
|
||||
val model = Var(TestModel())
|
||||
val el =
|
||||
div(cls := model(_.cls))
|
||||
def handleCommand(cmd: UpdateCmd): Unit = model.update(cmd.f)
|
||||
|
||||
lv.el.syncAll()
|
||||
lv.el.setAllUnchanged()
|
||||
|
||||
test("init") {
|
||||
assertEqualsDiff(
|
||||
lv.el,
|
||||
Json
|
||||
.Obj(
|
||||
"s" -> Json
|
||||
.Arr(Json.Str("<div class=\""), Json.Str("\"></div>")),
|
||||
"0" -> Json.Str("text-sm")
|
||||
),
|
||||
trackChanges = false
|
||||
)
|
||||
}
|
||||
test("diff no update") {
|
||||
assertEqualsDiff(lv.el, emptyDiff)
|
||||
}
|
||||
test("diff with update") {
|
||||
lv.handleCommand(UpdateCmd(_.copy(cls = "text-md")))
|
||||
assertEqualsDiff(
|
||||
lv.el,
|
||||
Json.Obj("0" -> Json.Str("text-md"))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
test("when mod") {
|
||||
val lv =
|
||||
new LiveView[UpdateCmd]:
|
||||
val model = Var(TestModel())
|
||||
val el =
|
||||
div(
|
||||
model.when(_.bool)(
|
||||
div("static string", model(_.nestedTitle))
|
||||
)
|
||||
)
|
||||
def handleCommand(cmd: UpdateCmd): Unit = model.update(cmd.f)
|
||||
|
||||
lv.el.syncAll()
|
||||
lv.el.setAllUnchanged()
|
||||
|
||||
test("init") {
|
||||
assertEqualsDiff(
|
||||
lv.el,
|
||||
Json
|
||||
.Obj(
|
||||
"s" -> Json.Arr(Json.Str("<div>"), Json.Str("</div>")),
|
||||
"0" -> Json.Bool(false)
|
||||
),
|
||||
trackChanges = false
|
||||
)
|
||||
}
|
||||
test("diff no update") {
|
||||
assertEqualsDiff(lv.el, emptyDiff)
|
||||
}
|
||||
test("diff with unrelated update") {
|
||||
lv.handleCommand(UpdateCmd(_.copy(title = "title updated")))
|
||||
assertEqualsDiff(lv.el, emptyDiff)
|
||||
}
|
||||
test("diff when true and nested update") {
|
||||
lv.handleCommand(UpdateCmd(_.copy(bool = true)))
|
||||
assertEqualsDiff(
|
||||
lv.el,
|
||||
Json.Obj(
|
||||
"0" ->
|
||||
Json
|
||||
.Obj(
|
||||
"s" -> Json
|
||||
.Arr(Json.Str("<div>static string"), Json.Str("</div>")),
|
||||
"0" -> Json.Str("nested title value")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
test("diff when nested change") {
|
||||
lv.handleCommand(UpdateCmd(_.copy(bool = true)))
|
||||
lv.el.syncAll()
|
||||
lv.el.setAllUnchanged()
|
||||
lv.handleCommand(UpdateCmd(_.copy(bool = true, nestedTitle = "nested title updated")))
|
||||
assertEqualsDiff(
|
||||
lv.el,
|
||||
Json.Obj(
|
||||
"0" ->
|
||||
Json
|
||||
.Obj(
|
||||
"0" -> Json.Str("nested title updated")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
test("splitByIndex mod") {
|
||||
val initModel = TestModel(
|
||||
items = List(
|
||||
NestedModel("a", 10),
|
||||
NestedModel("b", 15),
|
||||
NestedModel("c", 20)
|
||||
)
|
||||
)
|
||||
val lv =
|
||||
new LiveView[UpdateCmd]:
|
||||
val model = Var(initModel)
|
||||
val el =
|
||||
div(
|
||||
ul(
|
||||
model(_.items).splitByIndex((_, elem) =>
|
||||
li(
|
||||
"Nom: ",
|
||||
elem(_.name),
|
||||
" Age: ",
|
||||
elem(_.age.toString)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
def handleCommand(cmd: UpdateCmd): Unit = model.update(cmd.f)
|
||||
|
||||
lv.el.syncAll()
|
||||
lv.el.setAllUnchanged()
|
||||
|
||||
test("init") {
|
||||
assertEqualsDiff(
|
||||
lv.el,
|
||||
Json
|
||||
.Obj(
|
||||
"s" -> Json.Arr(Json.Str("<div><ul>"), Json.Str("</ul></div>")),
|
||||
"0" -> Json.Obj(
|
||||
"s" -> Json.Arr(
|
||||
Json.Str("<li>Nom: "),
|
||||
Json.Str(" Age: "),
|
||||
Json.Str("</li>")
|
||||
),
|
||||
"d" -> Json.Obj(
|
||||
"0" -> Json.Obj(
|
||||
"0" -> Json.Str("a"),
|
||||
"1" -> Json.Str("10")
|
||||
),
|
||||
"1" -> Json.Obj(
|
||||
"0" -> Json.Str("b"),
|
||||
"1" -> Json.Str("15")
|
||||
),
|
||||
"2" -> Json.Obj(
|
||||
"0" -> Json.Str("c"),
|
||||
"1" -> Json.Str("20")
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
trackChanges = false
|
||||
)
|
||||
}
|
||||
test("diff no update") {
|
||||
assertEqualsDiff(lv.el, emptyDiff)
|
||||
}
|
||||
test("diff with unrelated update") {
|
||||
lv.handleCommand(UpdateCmd(_.copy(title = "title updated")))
|
||||
assertEqualsDiff(lv.el, emptyDiff)
|
||||
}
|
||||
test("diff with item changed") {
|
||||
lv.handleCommand(
|
||||
UpdateCmd(_.copy(items = initModel.items.updated(2, NestedModel("c", 99))))
|
||||
)
|
||||
assertEqualsDiff(
|
||||
lv.el,
|
||||
Json.Obj(
|
||||
"0" ->
|
||||
Json
|
||||
.Obj(
|
||||
"d" -> Json.Obj(
|
||||
"2" -> Json.Obj(
|
||||
"1" -> Json.Str("99")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
test("diff with item added") {
|
||||
lv.handleCommand(
|
||||
UpdateCmd(
|
||||
_.copy(items = initModel.items.appended(NestedModel("d", 35)))
|
||||
)
|
||||
)
|
||||
assertEqualsDiff(
|
||||
lv.el,
|
||||
Json.Obj(
|
||||
"0" ->
|
||||
Json
|
||||
.Obj(
|
||||
"d" -> Json.Obj(
|
||||
"3" -> Json.Obj(
|
||||
"0" -> Json.Str("d"),
|
||||
"1" -> Json.Str("35")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
test("diff with first item removed") {
|
||||
lv.handleCommand(
|
||||
UpdateCmd(
|
||||
_.copy(items = initModel.items.tail)
|
||||
)
|
||||
)
|
||||
assertEqualsDiff(
|
||||
lv.el,
|
||||
Json.Obj(
|
||||
"0" ->
|
||||
Json
|
||||
.Obj(
|
||||
"d" -> Json.Obj(
|
||||
"0" -> Json.Obj(
|
||||
"0" -> Json.Str("b"),
|
||||
"1" -> Json.Str("15")
|
||||
),
|
||||
"1" -> Json.Obj(
|
||||
"0" -> Json.Str("c"),
|
||||
"1" -> Json.Str("20")
|
||||
),
|
||||
"2" -> Json.Bool(false)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
test("diff all removed") {
|
||||
lv.handleCommand(UpdateCmd(_.copy(items = List.empty)))
|
||||
assertEqualsDiff(
|
||||
lv.el,
|
||||
Json.Obj(
|
||||
"0" ->
|
||||
Json
|
||||
.Obj(
|
||||
"d" -> Json.Obj(
|
||||
"0" -> Json.Bool(false),
|
||||
"1" -> Json.Bool(false),
|
||||
"2" -> Json.Bool(false)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
end LiveViewSpec
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue