From 75130439b86c35ff4b9b91d9994b24a2110e8061 Mon Sep 17 00:00:00 2001 From: Stephen Gutekanst Date: Sat, 9 Dec 2017 22:31:59 -0700 Subject: [PATCH] add new data-driven test suite + move all existing tests to it (#178) * testsuite: add new data-driven test suite implementation * move TestAddStylesheet to new test suite * move TestSetTitle to new test suite * move TestRenderBody_Nested to new test suite * move TestRenderBody_Standard_loading to new test suite * move TestRenderBody_Standard_loaded to new test suite * move TestRenderBody_ExpectsBody to new test suite * move TestRenderBody_RenderSkipper_Skip into new test suite * move TestRerender_Nested to new test suite * move TestRerender_Nested to new test suite * move TestRerender_identical to new test suite * move TestRerender_no_prevRender to new test suite * move TestHTML_reconcile_nil to new test suite * move TestHTML_reconcile_std to new test suite * testsuite: remove now unused sortedMapString function * testsuite: remove now unused mockObject type --- .gitignore | 1 + dom_test.go | 1934 +++-------------- testdata/TestAddStylesheet.want.txt | 7 + ...reconcile_nil__add_event_listener.want.txt | 7 + ...estHTML_reconcile_nil__attributes.want.txt | 7 + .../TestHTML_reconcile_nil__children.want.txt | 17 + ...econcile_nil__children_render_nil.want.txt | 17 + ...TML_reconcile_nil__create_element.want.txt | 5 + ..._reconcile_nil__create_element_ns.want.txt | 5 + ...L_reconcile_nil__create_text_node.want.txt | 5 + .../TestHTML_reconcile_nil__dataset.want.txt | 7 + ...estHTML_reconcile_nil__inner_html.want.txt | 6 + ...estHTML_reconcile_nil__properties.want.txt | 7 + .../TestHTML_reconcile_nil__style.want.txt | 7 + ...L_reconcile_std__attributes__diff.want.txt | 16 + ...reconcile_std__attributes__remove.want.txt | 16 + ...tHTML_reconcile_std__class__combo.want.txt | 18 + ...stHTML_reconcile_std__class__diff.want.txt | 16 + ...estHTML_reconcile_std__class__map.want.txt | 15 + ..._reconcile_std__class__map_toggle.want.txt | 15 + ...tHTML_reconcile_std__class__multi.want.txt | 16 + ...HTML_reconcile_std__class__remove.want.txt | 15 + ...HTML_reconcile_std__dataset__diff.want.txt | 16 + ...ML_reconcile_std__dataset__remove.want.txt | 16 + ...econcile_std__event_listener_diff.want.txt | 18 + ...L_reconcile_std__properties__diff.want.txt | 16 + ...reconcile_std__properties__remove.want.txt | 16 + ...d__properties__replaced_elem_diff.want.txt | 15 + ..._properties__replaced_elem_shared.want.txt | 15 + ...stHTML_reconcile_std__style__diff.want.txt | 16 + ...HTML_reconcile_std__style__remove.want.txt | 16 + ...TestHTML_reconcile_std__text_diff.want.txt | 6 + ...TML_reconcile_std__text_identical.want.txt | 5 + .../TestRenderBody_ExpectsBody__div.want.txt | 6 + .../TestRenderBody_ExpectsBody__nil.want.txt | 6 + .../TestRenderBody_ExpectsBody__text.want.txt | 6 + testdata/TestRenderBody_Nested.want.txt | 9 + ...TestRenderBody_RenderSkipper_Skip.want.txt | 1 + .../TestRenderBody_Standard_loaded.want.txt | 9 + .../TestRenderBody_Standard_loading.want.txt | 11 + ...erender_Nested__component_to_html.want.txt | 23 + ...erender_Nested__html_to_component.want.txt | 23 + .../TestRerender_Nested__new_child.want.txt | 23 + .../TestRerender_change__new_child.want.txt | 23 + testdata/TestRerender_identical.want.txt | 16 + testdata/TestRerender_no_prevRender.want.txt | 0 testdata/TestSetTitle.want.txt | 2 + testsuite_test.go | 277 +++ 48 files changed, 1081 insertions(+), 1668 deletions(-) create mode 100755 testdata/TestAddStylesheet.want.txt create mode 100755 testdata/TestHTML_reconcile_nil__add_event_listener.want.txt create mode 100755 testdata/TestHTML_reconcile_nil__attributes.want.txt create mode 100755 testdata/TestHTML_reconcile_nil__children.want.txt create mode 100755 testdata/TestHTML_reconcile_nil__children_render_nil.want.txt create mode 100755 testdata/TestHTML_reconcile_nil__create_element.want.txt create mode 100755 testdata/TestHTML_reconcile_nil__create_element_ns.want.txt create mode 100755 testdata/TestHTML_reconcile_nil__create_text_node.want.txt create mode 100755 testdata/TestHTML_reconcile_nil__dataset.want.txt create mode 100755 testdata/TestHTML_reconcile_nil__inner_html.want.txt create mode 100755 testdata/TestHTML_reconcile_nil__properties.want.txt create mode 100755 testdata/TestHTML_reconcile_nil__style.want.txt create mode 100755 testdata/TestHTML_reconcile_std__attributes__diff.want.txt create mode 100755 testdata/TestHTML_reconcile_std__attributes__remove.want.txt create mode 100755 testdata/TestHTML_reconcile_std__class__combo.want.txt create mode 100755 testdata/TestHTML_reconcile_std__class__diff.want.txt create mode 100755 testdata/TestHTML_reconcile_std__class__map.want.txt create mode 100755 testdata/TestHTML_reconcile_std__class__map_toggle.want.txt create mode 100755 testdata/TestHTML_reconcile_std__class__multi.want.txt create mode 100755 testdata/TestHTML_reconcile_std__class__remove.want.txt create mode 100755 testdata/TestHTML_reconcile_std__dataset__diff.want.txt create mode 100755 testdata/TestHTML_reconcile_std__dataset__remove.want.txt create mode 100755 testdata/TestHTML_reconcile_std__event_listener_diff.want.txt create mode 100755 testdata/TestHTML_reconcile_std__properties__diff.want.txt create mode 100755 testdata/TestHTML_reconcile_std__properties__remove.want.txt create mode 100755 testdata/TestHTML_reconcile_std__properties__replaced_elem_diff.want.txt create mode 100755 testdata/TestHTML_reconcile_std__properties__replaced_elem_shared.want.txt create mode 100755 testdata/TestHTML_reconcile_std__style__diff.want.txt create mode 100755 testdata/TestHTML_reconcile_std__style__remove.want.txt create mode 100755 testdata/TestHTML_reconcile_std__text_diff.want.txt create mode 100755 testdata/TestHTML_reconcile_std__text_identical.want.txt create mode 100755 testdata/TestRenderBody_ExpectsBody__div.want.txt create mode 100755 testdata/TestRenderBody_ExpectsBody__nil.want.txt create mode 100755 testdata/TestRenderBody_ExpectsBody__text.want.txt create mode 100755 testdata/TestRenderBody_Nested.want.txt create mode 100755 testdata/TestRenderBody_RenderSkipper_Skip.want.txt create mode 100755 testdata/TestRenderBody_Standard_loaded.want.txt create mode 100755 testdata/TestRenderBody_Standard_loading.want.txt create mode 100755 testdata/TestRerender_Nested__component_to_html.want.txt create mode 100755 testdata/TestRerender_Nested__html_to_component.want.txt create mode 100755 testdata/TestRerender_Nested__new_child.want.txt create mode 100755 testdata/TestRerender_change__new_child.want.txt create mode 100755 testdata/TestRerender_identical.want.txt create mode 100644 testdata/TestRerender_no_prevRender.want.txt create mode 100755 testdata/TestSetTitle.want.txt create mode 100644 testsuite_test.go diff --git a/.gitignore b/.gitignore index 63123fbe..94e5c618 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +testdata/*.got.txt .DS_store diff --git a/dom_test.go b/dom_test.go index 1473bc38..a8682001 100644 --- a/dom_test.go +++ b/dom_test.go @@ -2,18 +2,11 @@ package vecty import ( "fmt" - "sort" - "strings" "testing" "github.com/gopherjs/gopherjs/js" ) -var _ = func() bool { - isTest = true - return true -}() - type testCore struct{ Core } func (testCore) Render() ComponentOrHTML { return Tag("p") } @@ -99,572 +92,246 @@ func TestHTML_Node(t *testing.T) { // works as expected (i.e. that it updates nodes correctly). func TestHTML_reconcile_std(t *testing.T) { t.Run("text_identical", func(t *testing.T) { - h := Text("foobar") - hNode := &mockObject{} - h.node = hNode - prev := Text("foobar") - prevNode := &mockObject{} - prev.node = prevNode - h.reconcile(prev) - if h.node != prevNode { - t.Fatal("h.node != prevNode") - } + ts := testSuite(t, "TestHTML_reconcile_std__text_identical") + defer ts.done() + + init := Text("foobar") + init.reconcile(nil) + + target := Text("foobar") + target.reconcile(init) }) t.Run("text_diff", func(t *testing.T) { - want := "bar" - h := Text(want) - hNode := &mockObject{} - h.node = hNode - prev := Text("foo") - setNodeValue := "" - prevNode := &mockObject{ - set: func(key string, value interface{}) { - if key != "nodeValue" { - panic(`key != "nodeValue"`) - } - setNodeValue = value.(string) - }, - } - prev.node = prevNode - h.reconcile(prev) - if h.node != prevNode { - t.Fatal("h.node != prevNode") - } - if setNodeValue != want { - t.Fatalf("got %q want %q", setNodeValue, want) - } + ts := testSuite(t, "TestHTML_reconcile_std__text_diff") + defer ts.done() + + init := Text("bar") + init.reconcile(nil) + + target := Text("foo") + target.reconcile(init) }) t.Run("properties", func(t *testing.T) { cases := []struct { - name string - initHTML *HTML - initResult string - targetHTML *HTML - targetResult string + name string + initHTML *HTML + targetHTML *HTML + sortedLines [][2]int }{ { - name: "diff", - initHTML: Tag("div", Markup(Property("a", 1), Property("b", "2foobar"))), - initResult: "a:1 b:2foobar", - targetHTML: Tag("div", Markup(Property("a", 3), Property("b", "4foobar"))), - targetResult: "a:3 b:4foobar", + name: "diff", + initHTML: Tag("div", Markup(Property("a", 1), Property("b", "2foobar"))), + targetHTML: Tag("div", Markup(Property("a", 3), Property("b", "4foobar"))), + sortedLines: [][2]int{{3, 4}, {12, 13}}, }, { - name: "remove", - initHTML: Tag("div", Markup(Property("a", 1), Property("b", "2foobar"))), - initResult: "a:1 b:2foobar", - targetHTML: Tag("div", Markup(Property("a", 3))), - targetResult: "a:3", + name: "remove", + initHTML: Tag("div", Markup(Property("a", 1), Property("b", "2foobar"))), + targetHTML: Tag("div", Markup(Property("a", 3))), + sortedLines: [][2]int{{3, 4}}, }, { - name: "replaced_elem_diff", - initHTML: Tag("div", Markup(Property("a", 1), Property("b", "2foobar"))), - initResult: "a:1 b:2foobar", - targetHTML: Tag("span", Markup(Property("a", 3), Property("b", "4foobar"))), - targetResult: "a:3 b:4foobar", + name: "replaced_elem_diff", + initHTML: Tag("div", Markup(Property("a", 1), Property("b", "2foobar"))), + targetHTML: Tag("span", Markup(Property("a", 3), Property("b", "4foobar"))), + sortedLines: [][2]int{{3, 4}, {11, 12}}, }, { - name: "replaced_elem_shared", - initHTML: Tag("div", Markup(Property("a", 1), Property("b", "2foobar"))), - initResult: "a:1 b:2foobar", - targetHTML: Tag("span", Markup(Property("a", 1), Property("b", "4foobar"))), - targetResult: "a:1 b:4foobar", + name: "replaced_elem_shared", + initHTML: Tag("div", Markup(Property("a", 1), Property("b", "2foobar"))), + targetHTML: Tag("span", Markup(Property("a", 1), Property("b", "4foobar"))), + sortedLines: [][2]int{{3, 4}, {11, 12}}, }, } for _, tst := range cases { t.Run(tst.name, func(t *testing.T) { - initSet := make(map[string]interface{}) - targetSet := make(map[string]interface{}) - initElem := &mockObject{ - set: func(key string, value interface{}) { - initSet[key] = value - }, - delete: func(key string) { - delete(initSet, key) - }, - } - targetElem := &mockObject{ - set: func(key string, value interface{}) { - targetSet[key] = value - }, - delete: func(key string) { - delete(targetSet, key) - }, - } - wrapperFunc := func(obj jsObject) func(string, ...interface{}) jsObject { - return func(name string, args ...interface{}) jsObject { - if name != "createElement" { - panic(fmt.Sprintf("expected call to createElement, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "div" && args[0].(string) != "span" { - panic(`args[0].(string) != "div|span"`) - } - return obj - } - } - global = &mockObject{ - get: map[string]jsObject{ - "document": &mockObject{call: wrapperFunc(initElem)}, - }, - } + ts := testSuite(t, "TestHTML_reconcile_std__properties__"+tst.name) + defer ts.multiSortedDone(tst.sortedLines...) + tst.initHTML.reconcile(nil) - got := sortedMapString(initSet) - if got != tst.initResult { - t.Fatalf("got %q want %q", got, tst.initResult) - } - matchingTags := tst.initHTML.tag == tst.targetHTML.tag - if !matchingTags { - global = &mockObject{ - get: map[string]jsObject{ - "document": &mockObject{call: wrapperFunc(targetElem)}, - }, - } - } + ts.record("(first reconcile done)") tst.targetHTML.reconcile(tst.initHTML) - if matchingTags { - got = sortedMapString(initSet) - } else { - got = sortedMapString(targetSet) - } - if got != tst.targetResult { - t.Fatalf("got %q want %q", got, tst.targetResult) - } }) } }) t.Run("attributes", func(t *testing.T) { cases := []struct { - name string - initHTML *HTML - initResult string - targetHTML *HTML - targetResult string + name string + initHTML *HTML + targetHTML *HTML + sortedLines [][2]int }{ { - name: "diff", - initHTML: Tag("div", Markup(Attribute("a", 1), Attribute("b", "2foobar"))), - initResult: "a:1 b:2foobar", - targetHTML: Tag("div", Markup(Attribute("a", 3), Attribute("b", "4foobar"))), - targetResult: "a:3 b:4foobar", + name: "diff", + initHTML: Tag("div", Markup(Attribute("a", 1), Attribute("b", "2foobar"))), + targetHTML: Tag("div", Markup(Attribute("a", 3), Attribute("b", "4foobar"))), + sortedLines: [][2]int{{3, 4}, {12, 13}}, }, { - name: "remove", - initHTML: Tag("div", Markup(Attribute("a", 1), Attribute("b", "2foobar"))), - initResult: "a:1 b:2foobar", - targetHTML: Tag("div", Markup(Attribute("a", 3))), - targetResult: "a:3", + name: "remove", + initHTML: Tag("div", Markup(Attribute("a", 1), Attribute("b", "2foobar"))), + targetHTML: Tag("div", Markup(Attribute("a", 3))), + sortedLines: [][2]int{{3, 4}}, }, } for _, tst := range cases { t.Run(tst.name, func(t *testing.T) { - set := map[string]interface{}{} - div := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - switch name { - case "setAttribute": - if len(args) != 2 { - panic("setAttribute: len(args) != 2") - } - set[args[0].(string)] = args[1] - case "removeAttribute": - if len(args) != 1 { - panic("removeAttribute: len(args) != 1") - } - delete(set, args[0].(string)) - default: - panic(fmt.Sprintf("expected call to [setAttribute, removeAttribute], not %q", name)) - } - return nil - }, - } - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "createElement" { - panic(fmt.Sprintf("expected call to createElement, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "div" { - panic(`args[0].(string) != "div"`) - } - return div - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - }, - } + ts := testSuite(t, "TestHTML_reconcile_std__attributes__"+tst.name) + defer ts.multiSortedDone(tst.sortedLines...) + tst.initHTML.reconcile(nil) - got := sortedMapString(set) - if got != tst.initResult { - t.Fatalf("got %q want %q", got, tst.initResult) - } + ts.record("(first reconcile done)") tst.targetHTML.reconcile(tst.initHTML) - got = sortedMapString(set) - if got != tst.targetResult { - t.Fatalf("got %q want %q", got, tst.targetResult) - } }) } }) t.Run("class", func(t *testing.T) { cases := []struct { - name string - initHTML *HTML - initResult string - targetHTML *HTML - targetResult string + name string + initHTML *HTML + targetHTML *HTML + sortedLines [][2]int }{ { - name: "multi", - initHTML: Tag("div", Markup(Class("a"), Class("b"))), - initResult: "a:true b:true", - targetHTML: Tag("div", Markup(Class("a"), Class("c"))), - targetResult: "a:true c:true", + name: "multi", + initHTML: Tag("div", Markup(Class("a"), Class("b"))), + targetHTML: Tag("div", Markup(Class("a"), Class("c"))), + sortedLines: [][2]int{{4, 5}}, }, { - name: "diff", - initHTML: Tag("div", Markup(Class("a", "b"))), - initResult: "a:true b:true", - targetHTML: Tag("div", Markup(Class("a", "c"))), - targetResult: "a:true c:true", + name: "diff", + initHTML: Tag("div", Markup(Class("a", "b"))), + targetHTML: Tag("div", Markup(Class("a", "c"))), + sortedLines: [][2]int{{4, 5}}, }, { - name: "remove", - initHTML: Tag("div", Markup(Class("a", "b"))), - initResult: "a:true b:true", - targetHTML: Tag("div", Markup(Class("a"))), - targetResult: "a:true", + name: "remove", + initHTML: Tag("div", Markup(Class("a", "b"))), + targetHTML: Tag("div", Markup(Class("a"))), + sortedLines: [][2]int{{4, 5}}, }, { - name: "map", - initHTML: Tag("div", Markup(ClassMap{"a": true, "b": true})), - initResult: "a:true b:true", - targetHTML: Tag("div", Markup(ClassMap{"a": true})), - targetResult: "a:true", + name: "map", + initHTML: Tag("div", Markup(ClassMap{"a": true, "b": true})), + targetHTML: Tag("div", Markup(ClassMap{"a": true})), + sortedLines: [][2]int{{4, 5}}, }, { - name: "map_toggle", - initHTML: Tag("div", Markup(ClassMap{"a": true, "b": true})), - initResult: "a:true b:true", - targetHTML: Tag("div", Markup(ClassMap{"a": true, "b": false})), - targetResult: "a:true", + name: "map_toggle", + initHTML: Tag("div", Markup(ClassMap{"a": true, "b": true})), + targetHTML: Tag("div", Markup(ClassMap{"a": true, "b": false})), + sortedLines: [][2]int{{4, 5}}, }, { - name: "combo", - initHTML: Tag("div", Markup(ClassMap{"a": true, "b": true}, Class("c"))), - initResult: "a:true b:true c:true", - targetHTML: Tag("div", Markup(ClassMap{"a": true, "b": false}, Class("d"))), - targetResult: "a:true d:true", + name: "combo", + initHTML: Tag("div", Markup(ClassMap{"a": true, "b": true}, Class("c"))), + targetHTML: Tag("div", Markup(ClassMap{"a": true, "b": false}, Class("d"))), + sortedLines: [][2]int{{4, 6}, {11, 12}}, }, } for _, tst := range cases { t.Run(tst.name, func(t *testing.T) { - set := map[string]interface{}{} - classList := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if len(args) != 1 { - panic("len(args) != 1") - } - if _, ok := args[0].(string); !ok { - panic("args[0].(string) is not string") - } - switch name { - case "add": - set[args[0].(string)] = true - case "remove": - delete(set, args[0].(string)) - default: - panic(fmt.Sprintf("expected call to add|remove, not %q", name)) - } - return nil - }, - } - div := &mockObject{ - get: map[string]jsObject{ - "classList": classList, - }, - } - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "createElement" { - panic(fmt.Sprintf("expected call to createElement, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "div" { - panic(`args[0].(string) != "div"`) - } - return div - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - }, - } + ts := testSuite(t, "TestHTML_reconcile_std__class__"+tst.name) + defer ts.multiSortedDone(tst.sortedLines...) + tst.initHTML.reconcile(nil) - got := sortedMapString(set) - if got != tst.initResult { - t.Fatalf("got %q want %q", got, tst.initResult) - } + ts.record("(first reconcile done)") tst.targetHTML.reconcile(tst.initHTML) - got = sortedMapString(set) - if got != tst.targetResult { - t.Fatalf("got %q want %q", got, tst.targetResult) - } }) } }) t.Run("dataset", func(t *testing.T) { cases := []struct { - name string - initHTML *HTML - initResult string - targetHTML *HTML - targetResult string + name string + initHTML *HTML + targetHTML *HTML + sortedLines [][2]int }{ { - name: "diff", - initHTML: Tag("div", Markup(Data("a", "1"), Data("b", "2foobar"))), - initResult: "a:1 b:2foobar", - targetHTML: Tag("div", Markup(Data("a", "3"), Data("b", "4foobar"))), - targetResult: "a:3 b:4foobar", + name: "diff", + initHTML: Tag("div", Markup(Data("a", "1"), Data("b", "2foobar"))), + targetHTML: Tag("div", Markup(Data("a", "3"), Data("b", "4foobar"))), + sortedLines: [][2]int{{5, 6}, {14, 15}}, }, { - name: "remove", - initHTML: Tag("div", Markup(Data("a", "1"), Data("b", "2foobar"))), - initResult: "a:1 b:2foobar", - targetHTML: Tag("div", Markup(Data("a", "3"))), - targetResult: "a:3", + name: "remove", + initHTML: Tag("div", Markup(Data("a", "1"), Data("b", "2foobar"))), + targetHTML: Tag("div", Markup(Data("a", "3"))), + sortedLines: [][2]int{{5, 6}}, }, } for _, tst := range cases { t.Run(tst.name, func(t *testing.T) { - set := map[string]interface{}{} - dataset := &mockObject{ - set: func(key string, value interface{}) { - set[key] = value - }, - delete: func(key string) { - delete(set, key) - }, - } - div := &mockObject{ - get: map[string]jsObject{ - "dataset": dataset, - }, - } - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "createElement" { - panic(fmt.Sprintf("expected call to createElement, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "div" { - panic(`args[0].(string) != "div"`) - } - return div - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - }, - } + ts := testSuite(t, "TestHTML_reconcile_std__dataset__"+tst.name) + defer ts.multiSortedDone(tst.sortedLines...) + tst.initHTML.reconcile(nil) - got := sortedMapString(set) - if got != tst.initResult { - t.Fatalf("got %q want %q", got, tst.initResult) - } + ts.record("(first reconcile done)") tst.targetHTML.reconcile(tst.initHTML) - got = sortedMapString(set) - if got != tst.targetResult { - t.Fatalf("got %q want %q", got, tst.targetResult) - } }) } }) t.Run("style", func(t *testing.T) { cases := []struct { - name string - initHTML *HTML - initResult string - targetHTML *HTML - targetResult string + name string + initHTML *HTML + targetHTML *HTML + sortedLines [][2]int }{ { - name: "diff", - initHTML: Tag("div", Markup(Style("a", "1"), Style("b", "2foobar"))), - initResult: "a:1 b:2foobar", - targetHTML: Tag("div", Markup(Style("a", "3"), Style("b", "4foobar"))), - targetResult: "a:3 b:4foobar", + name: "diff", + initHTML: Tag("div", Markup(Style("a", "1"), Style("b", "2foobar"))), + targetHTML: Tag("div", Markup(Style("a", "3"), Style("b", "4foobar"))), + sortedLines: [][2]int{{6, 7}, {15, 16}}, }, { - name: "remove", - initHTML: Tag("div", Markup(Style("a", "1"), Style("b", "2foobar"))), - initResult: "a:1 b:2foobar", - targetHTML: Tag("div", Markup(Style("a", "3"))), - targetResult: "a:3", + name: "remove", + initHTML: Tag("div", Markup(Style("a", "1"), Style("b", "2foobar"))), + targetHTML: Tag("div", Markup(Style("a", "3"))), + sortedLines: [][2]int{{6, 7}}, }, } for _, tst := range cases { t.Run(tst.name, func(t *testing.T) { - set := map[string]interface{}{} - style := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - switch name { - case "setProperty": - if len(args) != 2 { - panic("setProperty: len(args) != 2") - } - set[args[0].(string)] = args[1] - case "removeProperty": - if len(args) != 1 { - panic("removeProperty: len(args) != 1") - } - delete(set, args[0].(string)) - default: - panic(fmt.Sprintf("expected call to [setProperty, removeProperty], not %q", name)) - } - return nil - }, - } - div := &mockObject{ - get: map[string]jsObject{ - "style": style, - }, - } - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "createElement" { - panic(fmt.Sprintf("expected call to createElement, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "div" { - panic(`args[0].(string) != "div"`) - } - return div - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - }, - } + ts := testSuite(t, "TestHTML_reconcile_std__style__"+tst.name) + defer ts.multiSortedDone(tst.sortedLines...) + tst.initHTML.reconcile(nil) - got := sortedMapString(set) - if got != tst.initResult { - t.Fatalf("got %q want %q", got, tst.initResult) - } + ts.record("(first reconcile done)") tst.targetHTML.reconcile(tst.initHTML) - got = sortedMapString(set) - if got != tst.targetResult { - t.Fatalf("got %q want %q", got, tst.targetResult) - } }) } }) t.Run("event_listener", func(t *testing.T) { // TODO(pdf): Mock listener functions for equality testing - cases := []struct { - name string - initEventListeners []Applyer - targetEventListeners []Applyer - }{ - { - name: "diff", - initEventListeners: []Applyer{ - &EventListener{Name: "click"}, - &EventListener{Name: "keydown"}, - }, - targetEventListeners: []Applyer{ - &EventListener{Name: "click"}, - }, - }, + ts := testSuite(t, "TestHTML_reconcile_std__event_listener_diff") + defer ts.done() + + initEventListeners := []Applyer{ + &EventListener{Name: "click"}, + &EventListener{Name: "keydown"}, } - for _, tst := range cases { - t.Run(tst.name, func(t *testing.T) { - addedListeners := map[string]func(*js.Object){} - div := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - switch name { - case "addEventListener": - if len(args) != 2 { - panic("addEventListener: len(args) != 2") - } - addedListeners[args[0].(string)] = args[1].(func(*js.Object)) - case "removeEventListener": - if len(args) != 2 { - panic("removeEventListener: len(args) != 2") - } - delete(addedListeners, args[0].(string)) - default: - panic(fmt.Sprintf("unexpected call to %q", name)) - } - return nil - }, - } - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - switch name { - case "createElement": - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "div" { - panic(`args[0].(string) != "div"`) - } - return div - default: - panic(fmt.Sprintf("unexpected call to %q", name)) - } - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - }, - } - prev := Tag("div", Markup(tst.initEventListeners...)) - prev.reconcile(nil) - for i, m := range tst.initEventListeners { - listener := m.(*EventListener) - if listener.wrapper == nil { - t.Fatalf("listener %d wrapper == nil: %+v", i, listener) - } - if _, ok := addedListeners[listener.Name]; !ok { - t.Fatalf("listener %d for %q not found: %+v", i, listener.Name, listener) - } - } - if len(tst.initEventListeners) != len(addedListeners) { - t.Fatalf("listener count mismatch: %d != %d", len(tst.initEventListeners), len(addedListeners)) - } - h := Tag("div", Markup(tst.targetEventListeners...)) - h.reconcile(prev) - for i, m := range tst.targetEventListeners { - listener := m.(*EventListener) - if listener.wrapper == nil { - t.Fatalf("listener %d wrapper == nil: %+v", i, listener) - } - if _, ok := addedListeners[listener.Name]; !ok { - t.Fatalf("listener %d for %q not found: %+v", i, listener.Name, listener) - } - } - if len(tst.targetEventListeners) != len(addedListeners) { - t.Fatalf("listener count mismatch: %d != %d", len(tst.targetEventListeners), len(addedListeners)) - } - }) + prev := Tag("div", Markup(initEventListeners...)) + prev.reconcile(nil) + ts.record("(expected two added event listeners above)") + for i, m := range initEventListeners { + listener := m.(*EventListener) + if listener.wrapper == nil { + t.Fatalf("listener %d wrapper == nil: %+v", i, listener) + } + } + + targetEventListeners := []Applyer{ + &EventListener{Name: "click"}, + } + h := Tag("div", Markup(targetEventListeners...)) + h.reconcile(prev) + ts.record("(expected two removed, one added event listeners above)") + for i, m := range targetEventListeners { + listener := m.(*EventListener) + if listener.wrapper == nil { + t.Fatalf("listener %d wrapper == nil: %+v", i, listener) + } } }) @@ -696,325 +363,65 @@ func TestHTML_reconcile_nil(t *testing.T) { } }) t.Run("create_element", func(t *testing.T) { - strong := &mockObject{} - createdElement := "" - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "createElement" { - panic(fmt.Sprintf("expected call to createElement, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - createdElement = args[0].(string) - return strong - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - }, - } - want := "strong" - h := Tag(want) + ts := testSuite(t, "TestHTML_reconcile_nil__create_element") + defer ts.done() + + h := Tag("strong") h.reconcile(nil) - if createdElement != want { - t.Fatalf("createdElement %q want %q", createdElement, want) - } }) t.Run("create_element_ns", func(t *testing.T) { - strong := &mockObject{} - createdNamespace := "" - createdElement := "" - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "createElementNS" { - panic(fmt.Sprintf("expected call to createElementNS, not %q", name)) - } - if len(args) != 2 { - panic("len(args) != 2") - } - createdNamespace = args[0].(string) - createdElement = args[1].(string) - return strong - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - }, - } - wantTag := "strong" - wantNamespace := "foobar" - h := Tag(wantTag, Markup(Namespace(wantNamespace))) + ts := testSuite(t, "TestHTML_reconcile_nil__create_element_ns") + defer ts.done() + + h := Tag("strong", Markup(Namespace("foobar"))) h.reconcile(nil) - if createdElement != wantTag { - t.Fatalf("createdElement %q want tag %q", createdElement, wantTag) - } - if createdNamespace != wantNamespace { - t.Fatalf("createdNamespace %q want namespace %q", createdElement, wantNamespace) - } }) t.Run("create_text_node", func(t *testing.T) { - textNode := &mockObject{} - createdTextNode := "" - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "createTextNode" { - panic(fmt.Sprintf("expected call to createTextNode, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - createdTextNode = args[0].(string) - return textNode - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - }, - } - want := "hello" - h := &HTML{text: want} + ts := testSuite(t, "TestHTML_reconcile_nil__create_text_node") + defer ts.done() + + h := Text("hello") h.reconcile(nil) - if createdTextNode != want { - t.Fatalf("createdTextNode %q want %q", createdTextNode, want) - } }) t.Run("inner_html", func(t *testing.T) { - setInnerHTML := "" - div := &mockObject{ - set: func(key string, value interface{}) { - if key != "innerHTML" { - panic(fmt.Sprintf(`expected document.set "innerHTML", not %q`, key)) - } - setInnerHTML = value.(string) - }, - } - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "createElement" { - panic(fmt.Sprintf("expected call to createElement, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "div" { - panic(`args[0].(string) != "div"`) - } - return div - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - }, - } - want := "

hello

" - h := Tag("div", Markup(UnsafeHTML(want))) + ts := testSuite(t, "TestHTML_reconcile_nil__inner_html") + defer ts.done() + + h := Tag("div", Markup(UnsafeHTML("

hello

"))) h.reconcile(nil) - if setInnerHTML != want { - t.Fatalf("setInnerHTML %q want %q", setInnerHTML, want) - } }) t.Run("properties", func(t *testing.T) { - set := map[string]interface{}{} - div := &mockObject{ - set: func(key string, value interface{}) { - set[key] = value - }, - } - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "createElement" { - panic(fmt.Sprintf("expected call to createElement, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "div" { - panic(`args[0].(string) != "div"`) - } - return div - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - }, - } + ts := testSuite(t, "TestHTML_reconcile_nil__properties") + defer ts.sortedDone(3, 4) + h := Tag("div", Markup(Property("a", 1), Property("b", "2foobar"))) h.reconcile(nil) - got := sortedMapString(set) - want := "a:1 b:2foobar" - if got != want { - t.Fatalf("got %q want %q", got, want) - } }) t.Run("attributes", func(t *testing.T) { - set := map[string]interface{}{} - div := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "setAttribute" { - panic(fmt.Sprintf("expected call to setAttribute, not %q", name)) - } - if len(args) != 2 { - panic("len(args) != 2") - } - set[args[0].(string)] = args[1] - return nil - }, - } - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "createElement" { - panic(fmt.Sprintf("expected call to createElement, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "div" { - panic(`args[0].(string) != "div"`) - } - return div - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - }, - } + ts := testSuite(t, "TestHTML_reconcile_nil__attributes") + defer ts.sortedDone(3, 4) + h := Tag("div", Markup(Attribute("a", 1), Attribute("b", "2foobar"))) h.reconcile(nil) - got := sortedMapString(set) - want := "a:1 b:2foobar" - if got != want { - t.Fatalf("got %q want %q", got, want) - } }) t.Run("dataset", func(t *testing.T) { - set := map[string]interface{}{} - dataset := &mockObject{ - set: func(key string, value interface{}) { - set[key] = value - }, - } - div := &mockObject{ - get: map[string]jsObject{ - "dataset": dataset, - }, - } - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "createElement" { - panic(fmt.Sprintf("expected call to createElement, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "div" { - panic(`args[0].(string) != "div"`) - } - return div - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - }, - } + ts := testSuite(t, "TestHTML_reconcile_nil__dataset") + defer ts.sortedDone(5, 6) + h := Tag("div", Markup(Data("a", "1"), Data("b", "2foobar"))) h.reconcile(nil) - got := sortedMapString(set) - want := "a:1 b:2foobar" - if got != want { - t.Fatalf("got %q want %q", got, want) - } }) t.Run("style", func(t *testing.T) { - set := map[string]interface{}{} - style := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "setProperty" { - panic(fmt.Sprintf("expected call to setProperty, not %q", name)) - } - if len(args) != 2 { - panic("len(args) != 2") - } - set[args[0].(string)] = args[1] - return nil - }, - } - div := &mockObject{ - get: map[string]jsObject{ - "style": style, - }, - } - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "createElement" { - panic(fmt.Sprintf("expected call to createElement, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "div" { - panic(`args[0].(string) != "div"`) - } - return div - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - }, - } + ts := testSuite(t, "TestHTML_reconcile_nil__style") + defer ts.sortedDone(6, 7) + h := Tag("div", Markup(Style("a", "1"), Style("b", "2foobar"))) h.reconcile(nil) - got := sortedMapString(set) - want := "a:1 b:2foobar" - if got != want { - t.Fatalf("got %q want %q", got, want) - } }) t.Run("add_event_listener", func(t *testing.T) { - addedListeners := map[string]func(*js.Object){} - div := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - switch name { - case "addEventListener": - if len(args) != 2 { - panic("len(args) != 2") - } - addedListeners[args[0].(string)] = args[1].(func(*js.Object)) - return nil - default: - panic(fmt.Sprintf("unexpected call to %q", name)) - } - }, - } - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - switch name { - case "createElement": - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "div" { - panic(`args[0].(string) != "div"`) - } - return div - default: - panic(fmt.Sprintf("unexpected call to %q", name)) - } - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - }, - } + ts := testSuite(t, "TestHTML_reconcile_nil__add_event_listener") + defer ts.done() + e0 := &EventListener{Name: "click"} e1 := &EventListener{Name: "keydown"} h := Tag("div", Markup(e0, e1)) @@ -1025,56 +432,12 @@ func TestHTML_reconcile_nil(t *testing.T) { if e1.wrapper == nil { t.Fatal("e1.wrapper == nil") } - if gotE0 := addedListeners["click"]; gotE0 == nil { - t.Fatal("gotE0 == nil") - } - if gotE1 := addedListeners["keydown"]; gotE1 == nil { - t.Fatal("gotE1 == nil") - } }) t.Run("children", func(t *testing.T) { - var ( - divs []jsObject - appends = map[jsObject]jsObject{} - ) - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - switch name { - case "createElement": - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "div" { - panic(`args[0].(string) != "div"`) - } - div := &mockObject{} - divs = append(divs, div) - div.call = func(name string, args ...interface{}) jsObject { - switch name { - case "appendChild": - if len(args) != 1 { - panic("len(args) != 1") - } - appends[div] = args[0].(jsObject) - return nil - default: - panic(fmt.Sprintf("unexpected call to %q", name)) - } - } - return div - default: - panic(fmt.Sprintf("unexpected call to %q", name)) - } - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - }, - } - var ( - compRenderCalls int - ) + ts := testSuite(t, "TestHTML_reconcile_nil__children") + defer ts.done() + + var compRenderCalls int compRender := Tag("div") comp := &componentFunc{ id: "foobar", @@ -1085,9 +448,6 @@ func TestHTML_reconcile_nil(t *testing.T) { } h := Tag("div", Tag("div", comp)) h.reconcile(nil) - if len(divs) != 3 { - t.Fatal("len(divs) != 3") - } if compRenderCalls != 1 { t.Fatal("compRenderCalls != 1") } @@ -1097,59 +457,12 @@ func TestHTML_reconcile_nil(t *testing.T) { if comp.Context().prevRender != compRender { t.Fatal("comp.Context().prevRender != compRender") } - root := divs[0] - child := divs[1] - child2 := divs[2] - if appends[root] != child { - t.Fatal("appends[root] != child") - } - if appends[child] != child2 { - t.Fatal("appends[root] != child2") - } }) t.Run("children_render_nil", func(t *testing.T) { - var ( - nodes []jsObject - appends = map[jsObject]jsObject{} - ) - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - switch name { - case "createElement": - if len(args) != 1 { - panic("len(args) != 1") - } - if n := args[0].(string); n != "div" && n != "noscript" { - panic(`n != "div" && n != "noscript"`) - } - n := &mockObject{} - nodes = append(nodes, n) - n.call = func(name string, args ...interface{}) jsObject { - switch name { - case "appendChild": - if len(args) != 1 { - panic("len(args) != 1") - } - appends[n] = args[0].(jsObject) - return nil - default: - panic(fmt.Sprintf("unexpected call to %q", name)) - } - } - return n - default: - panic(fmt.Sprintf("unexpected call to %q", name)) - } - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - }, - } - var ( - compRenderCalls int - ) + ts := testSuite(t, "TestHTML_reconcile_nil__children_render_nil") + defer ts.done() + + var compRenderCalls int comp := &componentFunc{ id: "foobar", render: func() ComponentOrHTML { @@ -1159,9 +472,6 @@ func TestHTML_reconcile_nil(t *testing.T) { } h := Tag("div", Tag("div", comp)) h.reconcile(nil) - if len(nodes) != 3 { - t.Fatal("len(nodes) != 3") - } if compRenderCalls != 1 { t.Fatal("compRenderCalls != 1") } @@ -1171,15 +481,6 @@ func TestHTML_reconcile_nil(t *testing.T) { if comp.Context().prevRender == nil { t.Fatal("comp.Context().prevRender == nil") } - root := nodes[0] - child := nodes[1] - child2 := nodes[2] - if appends[root] != child { - t.Fatal("appends[root] != child") - } - if appends[child] != child2 { - t.Fatal("appends[root] != child2") - } }) } @@ -1239,35 +540,9 @@ func TestRerender_nil(t *testing.T) { // TestRerender_no_prevRender tests the behavior of Rerender when there is no // previous render. func TestRerender_no_prevRender(t *testing.T) { - var renderCallback func(float64) - global = &mockObject{ - get: map[string]jsObject{ - "performance": &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "now" { - panic(fmt.Sprintf("expected call to now, not %q", name)) - } - if len(args) != 0 { - panic("len(args) != 0") - } - return &mockObject{floatValue: 0} - }, - }, - }, - call: func(name string, args ...interface{}) jsObject { - if name != "requestAnimationFrame" { - panic(fmt.Sprintf("expected call to requestAnimationFrame, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - var ok bool - if renderCallback, ok = args[0].(func(float64)); !ok { - panic("incorrect argument to requestAnimationFrame") - } - return &mockObject{intValue: 0} - }, - } + ts := testSuite(t, "TestRerender_no_prevRender") + defer ts.done() + got := recoverStr(func() { Rerender(&componentFunc{ render: func() ComponentOrHTML { @@ -1277,7 +552,6 @@ func TestRerender_no_prevRender(t *testing.T) { panic("expected no SkipRender call") }, }) - renderCallback(0) }) want := "vecty: Rerender invoked on Component that has never been rendered" if got != want { @@ -1288,66 +562,13 @@ func TestRerender_no_prevRender(t *testing.T) { // TestRerender_identical tests the behavior of Rerender when there is a // previous render which is identical to the new render. func TestRerender_identical(t *testing.T) { - // Perform the initial render of the component. - body := &mockObject{} - bodySet := false - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "createElement" { - panic(fmt.Sprintf("expected call to createElement, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "body" { - panic(`args[0].(string) != "body"`) - } - return body - }, - get: map[string]jsObject{ - "readyState": &mockObject{stringValue: "complete"}, - }, - set: func(key string, value interface{}) { - if key != "body" { - panic(fmt.Sprintf(`expected document.set "body", not %q`, key)) - } - if value != body { - panic(fmt.Sprintf(`expected document.set body value, not %T %+v`, value, value)) - } - bodySet = true - }, - } - var renderCallback func(float64) - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - "performance": &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "now" { - panic(fmt.Sprintf("expected call to now, not %q", name)) - } - if len(args) != 0 { - panic("len(args) != 0") - } - return &mockObject{floatValue: 0} - }, - }, - }, - call: func(name string, args ...interface{}) jsObject { - if name != "requestAnimationFrame" { - panic(fmt.Sprintf("expected call to requestAnimationFrame, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - var ok bool - if renderCallback, ok = args[0].(func(float64)); !ok { - panic("incorrect argument to requestAnimationFrame") - } - return &mockObject{intValue: 0} - }, - } + ts := testSuite(t, "TestRerender_identical") + defer ts.done() + ts.ints.mock(`global.Call("requestAnimationFrame", func(float64))`, 0) + ts.strings.mock(`global.Get("document").Get("readyState")`, "complete") + + // Perform the initial render of the component. render := Tag("body") var renderCalled, skipRenderCalled int comp := &componentFunc{ @@ -1358,9 +579,6 @@ func TestRerender_identical(t *testing.T) { }, } RenderBody(comp) - if !bodySet { - t.Fatal("!bodySet") - } if renderCalled != 1 { t.Fatal("renderCalled != 1") } @@ -1392,8 +610,11 @@ func TestRerender_identical(t *testing.T) { return false } Rerender(comp) - renderCallback(0) - global = nil // Expecting no JS calls past here + + // Invoke the render callback. + ts.ints.mock(`global.Call("requestAnimationFrame", func(float64))`, 0) + ts.callbacks[`global.Call("requestAnimationFrame", func(float64))`].(func(float64))(0) + if renderCalled != 2 { t.Fatal("renderCalled != 2") } @@ -1428,80 +649,13 @@ func TestRerender_change(t *testing.T) { } for _, tst := range cases { t.Run(tst.name, func(t *testing.T) { - // Perform the initial render of the component. - var bodyAppendChild jsObject - body := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - switch name { - case "appendChild": - if len(args) != 1 { - panic("len(args) != 1") - } - bodyAppendChild = args[0].(jsObject) - return nil - default: - panic(fmt.Sprintf("unexpected call to %q", name)) - } - }, - } - bodySet := false - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "createElement" { - panic(fmt.Sprintf("expected call to createElement, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "body" { - panic(`args[0].(string) != "body"`) - } - return body - }, - get: map[string]jsObject{ - "readyState": &mockObject{stringValue: "complete"}, - }, - set: func(key string, value interface{}) { - if key != "body" { - panic(fmt.Sprintf(`expected document.set "body", not %q`, key)) - } - if value != body { - panic(fmt.Sprintf(`expected document.set body value, not %T %+v`, value, value)) - } - bodySet = true - }, - } - var renderCallback func(float64) - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - "performance": &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "now" { - panic(fmt.Sprintf("expected call to now, not %q", name)) - } - if len(args) != 0 { - panic("len(args) != 0") - } - return &mockObject{floatValue: 0} - }, - }, - }, - call: func(name string, args ...interface{}) jsObject { - if name != "requestAnimationFrame" { - panic(fmt.Sprintf("expected call to requestAnimationFrame, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - var ok bool - if renderCallback, ok = args[0].(func(float64)); !ok { - panic("incorrect argument to requestAnimationFrame") - } - return &mockObject{intValue: 0} - }, - } + ts := testSuite(t, "TestRerender_change__"+tst.name) + defer ts.done() + ts.ints.mock(`global.Call("requestAnimationFrame", func(float64))`, 0) + ts.strings.mock(`global.Get("document").Get("readyState")`, "complete") + + // Perform the initial render of the component. render := Tag("body") var renderCalled, skipRenderCalled int comp := &componentFunc{ @@ -1512,9 +666,7 @@ func TestRerender_change(t *testing.T) { }, } RenderBody(comp) - if !bodySet { - t.Fatal("!bodySet") - } + ts.record("(expect body to be set now)") if renderCalled != 1 { t.Fatal("renderCalled != 1") } @@ -1526,58 +678,6 @@ func TestRerender_change(t *testing.T) { } // Perform a re-render. - body = &mockObject{} - newNode := &mockObject{} - document = &mockObject{ - call: func(name string, args ...interface{}) jsObject { - switch name { - case "createElement": - if len(args) != 1 { - panic("len(args) != 1") - } - switch args[0].(string) { - case "body": - return body - case "div", "noscript": - return newNode - default: - panic("unexpected createElement call") - } - default: - panic(fmt.Sprintf("unexpected call to %q", name)) - } - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - "performance": &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "now" { - panic(fmt.Sprintf("expected call to now, not %q", name)) - } - if len(args) != 0 { - panic("len(args) != 0") - } - return &mockObject{floatValue: 0} - }, - }, - }, - call: func(name string, args ...interface{}) jsObject { - if name != "requestAnimationFrame" { - panic(fmt.Sprintf("expected call to requestAnimationFrame, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - var ok bool - if renderCallback, ok = args[0].(func(float64)); !ok { - panic("incorrect argument to requestAnimationFrame") - } - return &mockObject{intValue: 0} - }, - } - comp.id = "modified" comp.render = func() ComponentOrHTML { renderCalled++ @@ -1597,7 +697,11 @@ func TestRerender_change(t *testing.T) { return false } Rerender(comp) - renderCallback(0) + + // Invoke the render callback. + ts.ints.mock(`global.Call("requestAnimationFrame", func(float64))`, 0) + ts.callbacks[`global.Call("requestAnimationFrame", func(float64))`].(func(float64))(0) + if renderCalled != 2 { t.Fatal("renderCalled != 2") } @@ -1610,9 +714,6 @@ func TestRerender_change(t *testing.T) { if comp.Context().prevRenderComponent.(*componentFunc).id != "modified" { t.Fatal(`comp.Context().prevRenderComponent.(*componentFunc).id != "modified"`) } - if bodyAppendChild != newNode { - t.Fatal("bodyAppendChild != newNode") - } }) } } @@ -1651,80 +752,13 @@ func TestRerender_Nested(t *testing.T) { } for _, tst := range cases { t.Run(tst.name, func(t *testing.T) { - // Perform the initial render of the component. - var bodyAppendChild jsObject - body := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - switch name { - case "appendChild": - if len(args) != 1 { - panic("len(args) != 1") - } - bodyAppendChild = args[0].(jsObject) - return nil - default: - panic(fmt.Sprintf("unexpected call to %q", name)) - } - }, - } - bodySet := false - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "createElement" { - panic(fmt.Sprintf("expected call to createElement, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "body" { - panic(`args[0].(string) != "body"`) - } - return body - }, - get: map[string]jsObject{ - "readyState": &mockObject{stringValue: "complete"}, - }, - set: func(key string, value interface{}) { - if key != "body" { - panic(fmt.Sprintf(`expected document.set "body", not %q`, key)) - } - if value != body { - panic(fmt.Sprintf(`expected document.set body value, not %T %+v`, value, value)) - } - bodySet = true - }, - } - var renderCallback func(float64) - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - "performance": &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "now" { - panic(fmt.Sprintf("expected call to now, not %q", name)) - } - if len(args) != 0 { - panic("len(args) != 0") - } - return &mockObject{floatValue: 0} - }, - }, - }, - call: func(name string, args ...interface{}) jsObject { - if name != "requestAnimationFrame" { - panic(fmt.Sprintf("expected call to requestAnimationFrame, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - var ok bool - if renderCallback, ok = args[0].(func(float64)); !ok { - panic("incorrect argument to requestAnimationFrame") - } - return &mockObject{intValue: 0} - }, - } + ts := testSuite(t, "TestRerender_Nested__"+tst.name) + defer ts.done() + ts.ints.mock(`global.Call("requestAnimationFrame", func(float64))`, 0) + ts.strings.mock(`global.Get("document").Get("readyState")`, "complete") + + // Perform the initial render of the component. var renderCalled, skipRenderCalled int comp := &componentFunc{ id: "original", @@ -1734,9 +768,7 @@ func TestRerender_Nested(t *testing.T) { }, } RenderBody(comp) - if !bodySet { - t.Fatal("!bodySet") - } + ts.record("(expect body to be set now)") if renderCalled != 1 { t.Fatal("renderCalled != 1") } @@ -1748,58 +780,6 @@ func TestRerender_Nested(t *testing.T) { } // Perform a re-render. - body = &mockObject{} - newNode := &mockObject{} - document = &mockObject{ - call: func(name string, args ...interface{}) jsObject { - switch name { - case "createElement": - if len(args) != 1 { - panic("len(args) != 1") - } - switch args[0].(string) { - case "body": - return body - case "div", "noscript": - return newNode - default: - panic("unexpected createElement call") - } - default: - panic(fmt.Sprintf("unexpected call to %q", name)) - } - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - "performance": &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "now" { - panic(fmt.Sprintf("expected call to now, not %q", name)) - } - if len(args) != 0 { - panic("len(args) != 0") - } - return &mockObject{floatValue: 0} - }, - }, - }, - call: func(name string, args ...interface{}) jsObject { - if name != "requestAnimationFrame" { - panic(fmt.Sprintf("expected call to requestAnimationFrame, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - var ok bool - if renderCallback, ok = args[0].(func(float64)); !ok { - panic("incorrect argument to requestAnimationFrame") - } - return &mockObject{intValue: 0} - }, - } - comp.id = "modified" comp.render = func() ComponentOrHTML { renderCalled++ @@ -1819,10 +799,11 @@ func TestRerender_Nested(t *testing.T) { return false } Rerender(comp) - renderCallback(0) - if renderCalled != 2 { - t.Fatal("renderCalled != 2") - } + + // Invoke the render callback. + ts.ints.mock(`global.Call("requestAnimationFrame", func(float64))`, 0) + ts.callbacks[`global.Call("requestAnimationFrame", func(float64))`].(func(float64))(0) + if skipRenderCalled != 1 { t.Fatal("skipRenderCalled != 1") } @@ -1832,9 +813,6 @@ func TestRerender_Nested(t *testing.T) { if comp.Context().prevRenderComponent.(*componentFunc).id != "modified" { t.Fatal(`comp.Context().prevRenderComponent.(*componentFunc).id != "modified"`) } - if bodyAppendChild != newNode { - t.Fatal("bodyAppendChild != newNode") - } }) } } @@ -1865,47 +843,11 @@ func TestRenderBody_ExpectsBody(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - switch name { - case "createElement", "createTextNode": - if len(args) != 1 { - panic("len(args) != 1") - } - return &mockObject{} - default: - panic(fmt.Sprintf("unexpected call to %q", name)) - } - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - "performance": &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "now" { - panic(fmt.Sprintf("expected call to now, not %q", name)) - } - if len(args) != 0 { - panic("len(args) != 0") - } - return &mockObject{floatValue: 0} - }, - }, - }, - call: func(name string, args ...interface{}) jsObject { - if name != "requestAnimationFrame" { - panic(fmt.Sprintf("expected call to requestAnimationFrame, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if _, ok := args[0].(func(float64)); !ok { - panic("incorrect argument to requestAnimationFrame") - } - return &mockObject{intValue: 0} - }, - } + ts := testSuite(t, "TestRenderBody_ExpectsBody__"+c.name) + defer ts.done() + + ts.ints.mock(`global.Call("requestAnimationFrame", func(float64))`, 0) + var gotPanic string func() { defer func() { @@ -1931,60 +873,11 @@ func TestRenderBody_ExpectsBody(t *testing.T) { // TestRenderBody_RenderSkipper_Skip tests that RenderBody panics when the // component's SkipRender method returns skip == true. func TestRenderBody_RenderSkipper_Skip(t *testing.T) { - body := &mockObject{} - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "createElement" { - panic(fmt.Sprintf("expected call to createElement, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "body" { - panic(`args[0].(string) != "body"`) - } - return body - }, - get: map[string]jsObject{ - "readyState": &mockObject{stringValue: "complete"}, - }, - set: func(key string, value interface{}) { - if key != "body" { - panic(fmt.Sprintf(`expected document.set "body", not %q`, key)) - } - if value != body { - panic(fmt.Sprintf(`expected document.set body value, not %T %+v`, value, value)) - } - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - "performance": &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "now" { - panic(fmt.Sprintf("expected call to now, not %q", name)) - } - if len(args) != 0 { - panic("len(args) != 0") - } - return &mockObject{floatValue: 0} - }, - }, - }, - call: func(name string, args ...interface{}) jsObject { - if name != "requestAnimationFrame" { - panic(fmt.Sprintf("expected call to requestAnimationFrame, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if _, ok := args[0].(func(float64)); !ok { - panic("incorrect argument to requestAnimationFrame") - } - return &mockObject{intValue: 0} - }, - } + ts := testSuite(t, "TestRenderBody_RenderSkipper_Skip") + defer ts.done() + + ts.ints.mock(`global.Call("requestAnimationFrame", func(float64))`, 0) + comp := &componentFunc{ render: func() ComponentOrHTML { return Tag("body") @@ -2008,220 +901,48 @@ func TestRenderBody_RenderSkipper_Skip(t *testing.T) { // standard case of rendering into the "body" tag when the DOM is in a loaded // state. func TestRenderBody_Standard_loaded(t *testing.T) { - body := &mockObject{} - bodySet := false - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "createElement" { - panic(fmt.Sprintf("expected call to createElement, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "body" { - panic(`args[0].(string) != "body"`) - } - return body - }, - get: map[string]jsObject{ - "readyState": &mockObject{stringValue: "complete"}, - }, - set: func(key string, value interface{}) { - if key != "body" { - panic(fmt.Sprintf(`expected document.set "body", not %q`, key)) - } - if value != body { - panic(fmt.Sprintf(`expected document.set body value, not %T %+v`, value, value)) - } - bodySet = true - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - "performance": &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "now" { - panic(fmt.Sprintf("expected call to now, not %q", name)) - } - if len(args) != 0 { - panic("len(args) != 0") - } - return &mockObject{floatValue: 0} - }, - }, - }, - call: func(name string, args ...interface{}) jsObject { - if name != "requestAnimationFrame" { - panic(fmt.Sprintf("expected call to requestAnimationFrame, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if _, ok := args[0].(func(float64)); !ok { - panic("incorrect argument to requestAnimationFrame") - } - return &mockObject{intValue: 0} - }, - } + ts := testSuite(t, "TestRenderBody_Standard_loaded") + defer ts.done() + + ts.strings.mock(`global.Get("document").Get("readyState")`, "loaded") + ts.ints.mock(`global.Call("requestAnimationFrame", func(float64))`, 0) + RenderBody(&componentFunc{ render: func() ComponentOrHTML { return Tag("body") }, }) - if !bodySet { - t.Fatalf("expected document.body to be set") - } } // TestRenderBody_Standard_loading tests that RenderBody properly handles the // standard case of rendering into the "body" tag when the DOM is in a loading // state. func TestRenderBody_Standard_loading(t *testing.T) { - body := &mockObject{} - bodySet := false - var domLoadedEventListener func() - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - switch name { - case "createElement": - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "body" { - panic(`args[0].(string) != "body"`) - } - return body - case "addEventListener": - if len(args) != 2 { - panic("len(args) != 2") - } - if args[0].(string) != "DOMContentLoaded" { - panic(`args[0].(string) != "DOMContentLoaded"`) - } - domLoadedEventListener = args[1].(func()) - return nil - default: - panic(fmt.Sprintf("unexpected call to %q", name)) - } - }, - get: map[string]jsObject{ - "readyState": &mockObject{stringValue: "loading"}, - }, - set: func(key string, value interface{}) { - if key != "body" { - panic(fmt.Sprintf(`expected document.set "body", not %q`, key)) - } - if value != body { - panic(fmt.Sprintf(`expected document.set body value, not %T %+v`, value, value)) - } - bodySet = true - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - "performance": &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "now" { - panic(fmt.Sprintf("expected call to now, not %q", name)) - } - if len(args) != 0 { - panic("len(args) != 0") - } - return &mockObject{floatValue: 0} - }, - }, - }, - call: func(name string, args ...interface{}) jsObject { - if name != "requestAnimationFrame" { - panic(fmt.Sprintf("expected call to requestAnimationFrame, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if _, ok := args[0].(func(float64)); !ok { - panic("incorrect argument to requestAnimationFrame") - } - return &mockObject{intValue: 0} - }, - } + ts := testSuite(t, "TestRenderBody_Standard_loading") + defer ts.done() + + ts.strings.mock(`global.Get("document").Get("readyState")`, "loading") + ts.ints.mock(`global.Call("requestAnimationFrame", func(float64))`, 0) + RenderBody(&componentFunc{ render: func() ComponentOrHTML { return Tag("body") }, }) - if domLoadedEventListener == nil { - t.Fatalf("domLoadedEventListener == nil") - } - if bodySet { - t.Fatalf("expected document.body to NOT be set") - } - domLoadedEventListener() - if !bodySet { - t.Fatalf("expected document.body to be set") - } + + ts.record("(invoking DOMContentLoaded event listener)") + ts.callbacks[`global.Get("document").Call("addEventListener", "DOMContentLoaded", func())`].(func())() } // TestRenderBody_Nested tests that RenderBody properly handles nested // Components. func TestRenderBody_Nested(t *testing.T) { - body := &mockObject{} - bodySet := false - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "createElement" { - panic(fmt.Sprintf("expected call to createElement, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "body" { - panic(`args[0].(string) != "body"`) - } - return body - }, - get: map[string]jsObject{ - "readyState": &mockObject{stringValue: "complete"}, - }, - set: func(key string, value interface{}) { - if key != "body" { - panic(fmt.Sprintf(`expected document.set "body", not %q`, key)) - } - if value != body { - panic(fmt.Sprintf(`expected document.set body value, not %T %+v`, value, value)) - } - bodySet = true - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - "performance": &mockObject{ - call: func(name string, args ...interface{}) jsObject { - if name != "now" { - panic(fmt.Sprintf("expected call to now, not %q", name)) - } - if len(args) != 0 { - panic("len(args) != 0") - } - return &mockObject{floatValue: 0} - }, - }, - }, - call: func(name string, args ...interface{}) jsObject { - if name != "requestAnimationFrame" { - panic(fmt.Sprintf("expected call to requestAnimationFrame, not %q", name)) - } - if len(args) != 1 { - panic("len(args) != 1") - } - if _, ok := args[0].(func(float64)); !ok { - panic("incorrect argument to requestAnimationFrame") - } - return &mockObject{intValue: 0} - }, - } + ts := testSuite(t, "TestRenderBody_Nested") + defer ts.done() + + ts.strings.mock(`global.Get("document").Get("readyState")`, "complete") + ts.ints.mock(`global.Call("requestAnimationFrame", func(float64))`, 0) + RenderBody(&componentFunc{ render: func() ComponentOrHTML { return &componentFunc{ @@ -2235,145 +956,22 @@ func TestRenderBody_Nested(t *testing.T) { } }, }) - if !bodySet { - t.Fatalf("expected document.body to be set") - } } +// TestSetTitle tests that the SetTitle function performs the correct DOM +// operations. func TestSetTitle(t *testing.T) { - titleSet := "" - document := &mockObject{ - set: func(key string, value interface{}) { - if key != "title" { - panic(fmt.Sprintf(`expected document.set "title", not %q`, key)) - } - titleSet = value.(string) - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - }, - } - want := "foobar" - SetTitle(want) - if titleSet != want { - t.Fatalf("titleSet is %q, want %q", titleSet, want) - } -} + ts := testSuite(t, "TestSetTitle") + defer ts.done() -func TestAddStylesheet(t *testing.T) { - linkSet := map[string]interface{}{} - link := &mockObject{ - set: func(key string, value interface{}) { - linkSet[key] = value - }, - } - appendedToHead := false - head := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - switch name { - case "appendChild": - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0] != link { - panic(`args[0] != link`) - } - appendedToHead = true - return nil - default: - panic(fmt.Sprintf("unexpected call to %q", name)) - } - }, - } - document := &mockObject{ - call: func(name string, args ...interface{}) jsObject { - switch name { - case "createElement": - if len(args) != 1 { - panic("len(args) != 1") - } - if args[0].(string) != "link" { - panic(`args[0].(string) != "link"`) - } - return link - default: - panic(fmt.Sprintf("unexpected call to %q", name)) - } - }, - set: func(key string, value interface{}) { - if key != "title" { - panic(fmt.Sprintf(`expected document.set "title", not %q`, key)) - } - }, - get: map[string]jsObject{ - "head": head, - }, - } - global = &mockObject{ - get: map[string]jsObject{ - "document": document, - }, - } - url := "https://google.com/foobar.css" - AddStylesheet(url) - if !appendedToHead { - t.Fatal("expected link to be appended to document.head") - } - if linkSet["rel"] != "stylesheet" { - t.Fatal(`linkSet["rel"] != "stylesheet"`) - } - if linkSet["href"] != url { - t.Fatal(`linkSet["href"] != url`) - } + SetTitle("foobartitle") } -// sortedMapString returns the map converted to a string, but sorted. -func sortedMapString(m map[string]interface{}) string { - var strs []string - for k, v := range m { - strs = append(strs, fmt.Sprintf("%v:%v", k, v)) - } - sort.Strings(strs) - return strings.Join(strs, " ") -} - -// recoverStr runs f and returns the recovered panic as a string. -func recoverStr(f func()) (s string) { - defer func() { - s = fmt.Sprint(recover()) - }() - f() - return -} - -type componentFunc struct { - Core - id string - render func() ComponentOrHTML - skipRender func(prev Component) bool -} +// TestAddStylesheet tests that the AddStylesheet performs the correct DOM +// operations. +func TestAddStylesheet(t *testing.T) { + ts := testSuite(t, "TestAddStylesheet") + defer ts.done() -func (c *componentFunc) Render() ComponentOrHTML { return c.render() } -func (c *componentFunc) SkipRender(prev Component) bool { return c.skipRender(prev) } - -type mockObject struct { - set func(key string, value interface{}) - get map[string]jsObject - delete func(key string) - call func(name string, args ...interface{}) jsObject - stringValue string - boolValue bool - intValue int - floatValue float64 + AddStylesheet("https://google.com/foobar.css") } - -func (w *mockObject) Set(key string, value interface{}) { w.set(key, value) } -func (w *mockObject) Get(key string) jsObject { return w.get[key] } -func (w *mockObject) Delete(key string) { w.delete(key) } -func (w *mockObject) Call(name string, args ...interface{}) jsObject { return w.call(name, args...) } -func (w *mockObject) String() string { return w.stringValue } -func (w *mockObject) Bool() bool { return w.boolValue } -func (w *mockObject) Int() int { return w.intValue } -func (w *mockObject) Float() float64 { return w.floatValue } diff --git a/testdata/TestAddStylesheet.want.txt b/testdata/TestAddStylesheet.want.txt new file mode 100755 index 00000000..b42ddb50 --- /dev/null +++ b/testdata/TestAddStylesheet.want.txt @@ -0,0 +1,7 @@ +global.Get("document") +global.Get("document").Call("createElement", "link") +global.Get("document").Call("createElement", "link").Set("rel", "stylesheet") +global.Get("document").Call("createElement", "link").Set("href", "https://google.com/foobar.css") +global.Get("document") +global.Get("document").Get("head") +global.Get("document").Get("head").Call("appendChild", jsObject(global.Get("document").Call("createElement", "link"))) \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_nil__add_event_listener.want.txt b/testdata/TestHTML_reconcile_nil__add_event_listener.want.txt new file mode 100755 index 00000000..48c77b97 --- /dev/null +++ b/testdata/TestHTML_reconcile_nil__add_event_listener.want.txt @@ -0,0 +1,7 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Call("addEventListener", "click", func(*js.Object)) +global.Get("document").Call("createElement", "div").Call("addEventListener", "keydown", func(*js.Object)) \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_nil__attributes.want.txt b/testdata/TestHTML_reconcile_nil__attributes.want.txt new file mode 100755 index 00000000..95c04379 --- /dev/null +++ b/testdata/TestHTML_reconcile_nil__attributes.want.txt @@ -0,0 +1,7 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Call("setAttribute", "a", 1) +global.Get("document").Call("createElement", "div").Call("setAttribute", "b", "2foobar") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_nil__children.want.txt b/testdata/TestHTML_reconcile_nil__children.want.txt new file mode 100755 index 00000000..3618d995 --- /dev/null +++ b/testdata/TestHTML_reconcile_nil__children.want.txt @@ -0,0 +1,17 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Call("appendChild", jsObject(global.Get("document").Call("createElement", "div"))) +global.Get("document").Call("createElement", "div").Call("appendChild", jsObject(global.Get("document").Call("createElement", "div"))) \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_nil__children_render_nil.want.txt b/testdata/TestHTML_reconcile_nil__children_render_nil.want.txt new file mode 100755 index 00000000..79d7c476 --- /dev/null +++ b/testdata/TestHTML_reconcile_nil__children_render_nil.want.txt @@ -0,0 +1,17 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document") +global.Get("document").Call("createElement", "noscript") +global.Get("document").Call("createElement", "noscript").Get("classList") +global.Get("document").Call("createElement", "noscript").Get("dataset") +global.Get("document").Call("createElement", "noscript").Get("style") +global.Get("document").Call("createElement", "div").Call("appendChild", jsObject(global.Get("document").Call("createElement", "noscript"))) +global.Get("document").Call("createElement", "div").Call("appendChild", jsObject(global.Get("document").Call("createElement", "div"))) \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_nil__create_element.want.txt b/testdata/TestHTML_reconcile_nil__create_element.want.txt new file mode 100755 index 00000000..c4344f07 --- /dev/null +++ b/testdata/TestHTML_reconcile_nil__create_element.want.txt @@ -0,0 +1,5 @@ +global.Get("document") +global.Get("document").Call("createElement", "strong") +global.Get("document").Call("createElement", "strong").Get("classList") +global.Get("document").Call("createElement", "strong").Get("dataset") +global.Get("document").Call("createElement", "strong").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_nil__create_element_ns.want.txt b/testdata/TestHTML_reconcile_nil__create_element_ns.want.txt new file mode 100755 index 00000000..59ae4505 --- /dev/null +++ b/testdata/TestHTML_reconcile_nil__create_element_ns.want.txt @@ -0,0 +1,5 @@ +global.Get("document") +global.Get("document").Call("createElementNS", "foobar", "strong") +global.Get("document").Call("createElementNS", "foobar", "strong").Get("classList") +global.Get("document").Call("createElementNS", "foobar", "strong").Get("dataset") +global.Get("document").Call("createElementNS", "foobar", "strong").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_nil__create_text_node.want.txt b/testdata/TestHTML_reconcile_nil__create_text_node.want.txt new file mode 100755 index 00000000..6c94c374 --- /dev/null +++ b/testdata/TestHTML_reconcile_nil__create_text_node.want.txt @@ -0,0 +1,5 @@ +global.Get("document") +global.Get("document").Call("createTextNode", "hello") +global.Get("document").Call("createTextNode", "hello").Get("classList") +global.Get("document").Call("createTextNode", "hello").Get("dataset") +global.Get("document").Call("createTextNode", "hello").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_nil__dataset.want.txt b/testdata/TestHTML_reconcile_nil__dataset.want.txt new file mode 100755 index 00000000..23e3ed03 --- /dev/null +++ b/testdata/TestHTML_reconcile_nil__dataset.want.txt @@ -0,0 +1,7 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("dataset").Set("a", "1") +global.Get("document").Call("createElement", "div").Get("dataset").Set("b", "2foobar") +global.Get("document").Call("createElement", "div").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_nil__inner_html.want.txt b/testdata/TestHTML_reconcile_nil__inner_html.want.txt new file mode 100755 index 00000000..8e5d7a55 --- /dev/null +++ b/testdata/TestHTML_reconcile_nil__inner_html.want.txt @@ -0,0 +1,6 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Set("innerHTML", "

hello

") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_nil__properties.want.txt b/testdata/TestHTML_reconcile_nil__properties.want.txt new file mode 100755 index 00000000..16c59cc5 --- /dev/null +++ b/testdata/TestHTML_reconcile_nil__properties.want.txt @@ -0,0 +1,7 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Set("a", 1) +global.Get("document").Call("createElement", "div").Set("b", "2foobar") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_nil__style.want.txt b/testdata/TestHTML_reconcile_nil__style.want.txt new file mode 100755 index 00000000..cbc47828 --- /dev/null +++ b/testdata/TestHTML_reconcile_nil__style.want.txt @@ -0,0 +1,7 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Get("style").Call("setProperty", "a", "1") +global.Get("document").Call("createElement", "div").Get("style").Call("setProperty", "b", "2foobar") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__attributes__diff.want.txt b/testdata/TestHTML_reconcile_std__attributes__diff.want.txt new file mode 100755 index 00000000..da4ca2fd --- /dev/null +++ b/testdata/TestHTML_reconcile_std__attributes__diff.want.txt @@ -0,0 +1,16 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Call("setAttribute", "a", 1) +global.Get("document").Call("createElement", "div").Call("setAttribute", "b", "2foobar") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +(first reconcile done) +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Call("setAttribute", "a", 3) +global.Get("document").Call("createElement", "div").Call("setAttribute", "b", "4foobar") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__attributes__remove.want.txt b/testdata/TestHTML_reconcile_std__attributes__remove.want.txt new file mode 100755 index 00000000..a5710c41 --- /dev/null +++ b/testdata/TestHTML_reconcile_std__attributes__remove.want.txt @@ -0,0 +1,16 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Call("setAttribute", "a", 1) +global.Get("document").Call("createElement", "div").Call("setAttribute", "b", "2foobar") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +(first reconcile done) +global.Get("document").Call("createElement", "div").Call("removeAttribute", "b") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Call("setAttribute", "a", 3) +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__class__combo.want.txt b/testdata/TestHTML_reconcile_std__class__combo.want.txt new file mode 100755 index 00000000..31d919a8 --- /dev/null +++ b/testdata/TestHTML_reconcile_std__class__combo.want.txt @@ -0,0 +1,18 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("classList").Call("add", "a") +global.Get("document").Call("createElement", "div").Get("classList").Call("add", "b") +global.Get("document").Call("createElement", "div").Get("classList").Call("add", "c") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +(first reconcile done) +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("classList").Call("remove", "b") +global.Get("document").Call("createElement", "div").Get("classList").Call("remove", "c") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("classList").Call("add", "d") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__class__diff.want.txt b/testdata/TestHTML_reconcile_std__class__diff.want.txt new file mode 100755 index 00000000..85bdaa06 --- /dev/null +++ b/testdata/TestHTML_reconcile_std__class__diff.want.txt @@ -0,0 +1,16 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("classList").Call("add", "a") +global.Get("document").Call("createElement", "div").Get("classList").Call("add", "b") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +(first reconcile done) +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("classList").Call("remove", "b") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("classList").Call("add", "c") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__class__map.want.txt b/testdata/TestHTML_reconcile_std__class__map.want.txt new file mode 100755 index 00000000..31201e38 --- /dev/null +++ b/testdata/TestHTML_reconcile_std__class__map.want.txt @@ -0,0 +1,15 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("classList").Call("add", "a") +global.Get("document").Call("createElement", "div").Get("classList").Call("add", "b") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +(first reconcile done) +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("classList").Call("remove", "b") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__class__map_toggle.want.txt b/testdata/TestHTML_reconcile_std__class__map_toggle.want.txt new file mode 100755 index 00000000..31201e38 --- /dev/null +++ b/testdata/TestHTML_reconcile_std__class__map_toggle.want.txt @@ -0,0 +1,15 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("classList").Call("add", "a") +global.Get("document").Call("createElement", "div").Get("classList").Call("add", "b") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +(first reconcile done) +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("classList").Call("remove", "b") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__class__multi.want.txt b/testdata/TestHTML_reconcile_std__class__multi.want.txt new file mode 100755 index 00000000..85bdaa06 --- /dev/null +++ b/testdata/TestHTML_reconcile_std__class__multi.want.txt @@ -0,0 +1,16 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("classList").Call("add", "a") +global.Get("document").Call("createElement", "div").Get("classList").Call("add", "b") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +(first reconcile done) +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("classList").Call("remove", "b") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("classList").Call("add", "c") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__class__remove.want.txt b/testdata/TestHTML_reconcile_std__class__remove.want.txt new file mode 100755 index 00000000..31201e38 --- /dev/null +++ b/testdata/TestHTML_reconcile_std__class__remove.want.txt @@ -0,0 +1,15 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("classList").Call("add", "a") +global.Get("document").Call("createElement", "div").Get("classList").Call("add", "b") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +(first reconcile done) +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("classList").Call("remove", "b") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__dataset__diff.want.txt b/testdata/TestHTML_reconcile_std__dataset__diff.want.txt new file mode 100755 index 00000000..ed16c11f --- /dev/null +++ b/testdata/TestHTML_reconcile_std__dataset__diff.want.txt @@ -0,0 +1,16 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("dataset").Set("a", "1") +global.Get("document").Call("createElement", "div").Get("dataset").Set("b", "2foobar") +global.Get("document").Call("createElement", "div").Get("style") +(first reconcile done) +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("dataset").Set("a", "3") +global.Get("document").Call("createElement", "div").Get("dataset").Set("b", "4foobar") +global.Get("document").Call("createElement", "div").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__dataset__remove.want.txt b/testdata/TestHTML_reconcile_std__dataset__remove.want.txt new file mode 100755 index 00000000..f5408d5e --- /dev/null +++ b/testdata/TestHTML_reconcile_std__dataset__remove.want.txt @@ -0,0 +1,16 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("dataset").Set("a", "1") +global.Get("document").Call("createElement", "div").Get("dataset").Set("b", "2foobar") +global.Get("document").Call("createElement", "div").Get("style") +(first reconcile done) +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("dataset").Delete("b") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("dataset").Set("a", "3") +global.Get("document").Call("createElement", "div").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__event_listener_diff.want.txt b/testdata/TestHTML_reconcile_std__event_listener_diff.want.txt new file mode 100755 index 00000000..355253f2 --- /dev/null +++ b/testdata/TestHTML_reconcile_std__event_listener_diff.want.txt @@ -0,0 +1,18 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Call("addEventListener", "click", func(*js.Object)) +global.Get("document").Call("createElement", "div").Call("addEventListener", "keydown", func(*js.Object)) +(expected two added event listeners above) +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Call("removeEventListener", "click", func(*js.Object)) +global.Get("document").Call("createElement", "div").Call("removeEventListener", "keydown", func(*js.Object)) +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Call("addEventListener", "click", func(*js.Object)) +(expected two removed, one added event listeners above) \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__properties__diff.want.txt b/testdata/TestHTML_reconcile_std__properties__diff.want.txt new file mode 100755 index 00000000..7049fc9d --- /dev/null +++ b/testdata/TestHTML_reconcile_std__properties__diff.want.txt @@ -0,0 +1,16 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Set("a", 1) +global.Get("document").Call("createElement", "div").Set("b", "2foobar") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +(first reconcile done) +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Set("a", 3) +global.Get("document").Call("createElement", "div").Set("b", "4foobar") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__properties__remove.want.txt b/testdata/TestHTML_reconcile_std__properties__remove.want.txt new file mode 100755 index 00000000..0472b14f --- /dev/null +++ b/testdata/TestHTML_reconcile_std__properties__remove.want.txt @@ -0,0 +1,16 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Set("a", 1) +global.Get("document").Call("createElement", "div").Set("b", "2foobar") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +(first reconcile done) +global.Get("document").Call("createElement", "div").Delete("b") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Set("a", 3) +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__properties__replaced_elem_diff.want.txt b/testdata/TestHTML_reconcile_std__properties__replaced_elem_diff.want.txt new file mode 100755 index 00000000..c7f40876 --- /dev/null +++ b/testdata/TestHTML_reconcile_std__properties__replaced_elem_diff.want.txt @@ -0,0 +1,15 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Set("a", 1) +global.Get("document").Call("createElement", "div").Set("b", "2foobar") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +(first reconcile done) +global.Get("document") +global.Get("document").Call("createElement", "span") +global.Get("document").Call("createElement", "span").Set("a", 3) +global.Get("document").Call("createElement", "span").Set("b", "4foobar") +global.Get("document").Call("createElement", "span").Get("classList") +global.Get("document").Call("createElement", "span").Get("dataset") +global.Get("document").Call("createElement", "span").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__properties__replaced_elem_shared.want.txt b/testdata/TestHTML_reconcile_std__properties__replaced_elem_shared.want.txt new file mode 100755 index 00000000..4aac5d4d --- /dev/null +++ b/testdata/TestHTML_reconcile_std__properties__replaced_elem_shared.want.txt @@ -0,0 +1,15 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Set("a", 1) +global.Get("document").Call("createElement", "div").Set("b", "2foobar") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +(first reconcile done) +global.Get("document") +global.Get("document").Call("createElement", "span") +global.Get("document").Call("createElement", "span").Set("a", 1) +global.Get("document").Call("createElement", "span").Set("b", "4foobar") +global.Get("document").Call("createElement", "span").Get("classList") +global.Get("document").Call("createElement", "span").Get("dataset") +global.Get("document").Call("createElement", "span").Get("style") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__style__diff.want.txt b/testdata/TestHTML_reconcile_std__style__diff.want.txt new file mode 100755 index 00000000..70c83723 --- /dev/null +++ b/testdata/TestHTML_reconcile_std__style__diff.want.txt @@ -0,0 +1,16 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Get("style").Call("setProperty", "a", "1") +global.Get("document").Call("createElement", "div").Get("style").Call("setProperty", "b", "2foobar") +(first reconcile done) +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Get("style").Call("setProperty", "a", "3") +global.Get("document").Call("createElement", "div").Get("style").Call("setProperty", "b", "4foobar") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__style__remove.want.txt b/testdata/TestHTML_reconcile_std__style__remove.want.txt new file mode 100755 index 00000000..b1131eff --- /dev/null +++ b/testdata/TestHTML_reconcile_std__style__remove.want.txt @@ -0,0 +1,16 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Get("style").Call("setProperty", "a", "1") +global.Get("document").Call("createElement", "div").Get("style").Call("setProperty", "b", "2foobar") +(first reconcile done) +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Get("style").Call("removeProperty", "b") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "div").Get("style").Call("setProperty", "a", "3") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__text_diff.want.txt b/testdata/TestHTML_reconcile_std__text_diff.want.txt new file mode 100755 index 00000000..eee5aeb7 --- /dev/null +++ b/testdata/TestHTML_reconcile_std__text_diff.want.txt @@ -0,0 +1,6 @@ +global.Get("document") +global.Get("document").Call("createTextNode", "bar") +global.Get("document").Call("createTextNode", "bar").Get("classList") +global.Get("document").Call("createTextNode", "bar").Get("dataset") +global.Get("document").Call("createTextNode", "bar").Get("style") +global.Get("document").Call("createTextNode", "bar").Set("nodeValue", "foo") \ No newline at end of file diff --git a/testdata/TestHTML_reconcile_std__text_identical.want.txt b/testdata/TestHTML_reconcile_std__text_identical.want.txt new file mode 100755 index 00000000..4d4b3b11 --- /dev/null +++ b/testdata/TestHTML_reconcile_std__text_identical.want.txt @@ -0,0 +1,5 @@ +global.Get("document") +global.Get("document").Call("createTextNode", "foobar") +global.Get("document").Call("createTextNode", "foobar").Get("classList") +global.Get("document").Call("createTextNode", "foobar").Get("dataset") +global.Get("document").Call("createTextNode", "foobar").Get("style") \ No newline at end of file diff --git a/testdata/TestRenderBody_ExpectsBody__div.want.txt b/testdata/TestRenderBody_ExpectsBody__div.want.txt new file mode 100755 index 00000000..bce71bfd --- /dev/null +++ b/testdata/TestRenderBody_ExpectsBody__div.want.txt @@ -0,0 +1,6 @@ +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Call("requestAnimationFrame", func(float64)) \ No newline at end of file diff --git a/testdata/TestRenderBody_ExpectsBody__nil.want.txt b/testdata/TestRenderBody_ExpectsBody__nil.want.txt new file mode 100755 index 00000000..c5a03b61 --- /dev/null +++ b/testdata/TestRenderBody_ExpectsBody__nil.want.txt @@ -0,0 +1,6 @@ +global.Get("document") +global.Get("document").Call("createElement", "noscript") +global.Get("document").Call("createElement", "noscript").Get("classList") +global.Get("document").Call("createElement", "noscript").Get("dataset") +global.Get("document").Call("createElement", "noscript").Get("style") +global.Call("requestAnimationFrame", func(float64)) \ No newline at end of file diff --git a/testdata/TestRenderBody_ExpectsBody__text.want.txt b/testdata/TestRenderBody_ExpectsBody__text.want.txt new file mode 100755 index 00000000..2f7f77cb --- /dev/null +++ b/testdata/TestRenderBody_ExpectsBody__text.want.txt @@ -0,0 +1,6 @@ +global.Get("document") +global.Get("document").Call("createTextNode", "Hello world!") +global.Get("document").Call("createTextNode", "Hello world!").Get("classList") +global.Get("document").Call("createTextNode", "Hello world!").Get("dataset") +global.Get("document").Call("createTextNode", "Hello world!").Get("style") +global.Call("requestAnimationFrame", func(float64)) \ No newline at end of file diff --git a/testdata/TestRenderBody_Nested.want.txt b/testdata/TestRenderBody_Nested.want.txt new file mode 100755 index 00000000..019470df --- /dev/null +++ b/testdata/TestRenderBody_Nested.want.txt @@ -0,0 +1,9 @@ +global.Get("document") +global.Get("document").Call("createElement", "body") +global.Get("document").Call("createElement", "body").Get("classList") +global.Get("document").Call("createElement", "body").Get("dataset") +global.Get("document").Call("createElement", "body").Get("style") +global.Get("document") +global.Get("document").Get("readyState") +global.Get("document").Set("body", jsObject(global.Get("document").Call("createElement", "body"))) +global.Call("requestAnimationFrame", func(float64)) \ No newline at end of file diff --git a/testdata/TestRenderBody_RenderSkipper_Skip.want.txt b/testdata/TestRenderBody_RenderSkipper_Skip.want.txt new file mode 100755 index 00000000..d214cc82 --- /dev/null +++ b/testdata/TestRenderBody_RenderSkipper_Skip.want.txt @@ -0,0 +1 @@ +global.Call("requestAnimationFrame", func(float64)) \ No newline at end of file diff --git a/testdata/TestRenderBody_Standard_loaded.want.txt b/testdata/TestRenderBody_Standard_loaded.want.txt new file mode 100755 index 00000000..019470df --- /dev/null +++ b/testdata/TestRenderBody_Standard_loaded.want.txt @@ -0,0 +1,9 @@ +global.Get("document") +global.Get("document").Call("createElement", "body") +global.Get("document").Call("createElement", "body").Get("classList") +global.Get("document").Call("createElement", "body").Get("dataset") +global.Get("document").Call("createElement", "body").Get("style") +global.Get("document") +global.Get("document").Get("readyState") +global.Get("document").Set("body", jsObject(global.Get("document").Call("createElement", "body"))) +global.Call("requestAnimationFrame", func(float64)) \ No newline at end of file diff --git a/testdata/TestRenderBody_Standard_loading.want.txt b/testdata/TestRenderBody_Standard_loading.want.txt new file mode 100755 index 00000000..de1284a9 --- /dev/null +++ b/testdata/TestRenderBody_Standard_loading.want.txt @@ -0,0 +1,11 @@ +global.Get("document") +global.Get("document").Call("createElement", "body") +global.Get("document").Call("createElement", "body").Get("classList") +global.Get("document").Call("createElement", "body").Get("dataset") +global.Get("document").Call("createElement", "body").Get("style") +global.Get("document") +global.Get("document").Get("readyState") +global.Get("document").Call("addEventListener", "DOMContentLoaded", func()) +global.Call("requestAnimationFrame", func(float64)) +(invoking DOMContentLoaded event listener) +global.Get("document").Set("body", jsObject(global.Get("document").Call("createElement", "body"))) \ No newline at end of file diff --git a/testdata/TestRerender_Nested__component_to_html.want.txt b/testdata/TestRerender_Nested__component_to_html.want.txt new file mode 100755 index 00000000..d8adc033 --- /dev/null +++ b/testdata/TestRerender_Nested__component_to_html.want.txt @@ -0,0 +1,23 @@ +global.Get("document") +global.Get("document").Call("createElement", "body") +global.Get("document").Call("createElement", "body").Get("classList") +global.Get("document").Call("createElement", "body").Get("dataset") +global.Get("document").Call("createElement", "body").Get("style") +global.Get("document") +global.Get("document").Get("readyState") +global.Get("document").Set("body", jsObject(global.Get("document").Call("createElement", "body"))) +global.Call("requestAnimationFrame", func(float64)) +(expect body to be set now) +global.Get("document").Call("createElement", "body").Get("classList") +global.Get("document").Call("createElement", "body").Get("dataset") +global.Get("document").Call("createElement", "body").Get("style") +global.Get("document").Call("createElement", "body").Get("classList") +global.Get("document").Call("createElement", "body").Get("dataset") +global.Get("document").Call("createElement", "body").Get("style") +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "body").Call("appendChild", jsObject(global.Get("document").Call("createElement", "div"))) +global.Call("requestAnimationFrame", func(float64)) \ No newline at end of file diff --git a/testdata/TestRerender_Nested__html_to_component.want.txt b/testdata/TestRerender_Nested__html_to_component.want.txt new file mode 100755 index 00000000..d8adc033 --- /dev/null +++ b/testdata/TestRerender_Nested__html_to_component.want.txt @@ -0,0 +1,23 @@ +global.Get("document") +global.Get("document").Call("createElement", "body") +global.Get("document").Call("createElement", "body").Get("classList") +global.Get("document").Call("createElement", "body").Get("dataset") +global.Get("document").Call("createElement", "body").Get("style") +global.Get("document") +global.Get("document").Get("readyState") +global.Get("document").Set("body", jsObject(global.Get("document").Call("createElement", "body"))) +global.Call("requestAnimationFrame", func(float64)) +(expect body to be set now) +global.Get("document").Call("createElement", "body").Get("classList") +global.Get("document").Call("createElement", "body").Get("dataset") +global.Get("document").Call("createElement", "body").Get("style") +global.Get("document").Call("createElement", "body").Get("classList") +global.Get("document").Call("createElement", "body").Get("dataset") +global.Get("document").Call("createElement", "body").Get("style") +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "body").Call("appendChild", jsObject(global.Get("document").Call("createElement", "div"))) +global.Call("requestAnimationFrame", func(float64)) \ No newline at end of file diff --git a/testdata/TestRerender_Nested__new_child.want.txt b/testdata/TestRerender_Nested__new_child.want.txt new file mode 100755 index 00000000..d8adc033 --- /dev/null +++ b/testdata/TestRerender_Nested__new_child.want.txt @@ -0,0 +1,23 @@ +global.Get("document") +global.Get("document").Call("createElement", "body") +global.Get("document").Call("createElement", "body").Get("classList") +global.Get("document").Call("createElement", "body").Get("dataset") +global.Get("document").Call("createElement", "body").Get("style") +global.Get("document") +global.Get("document").Get("readyState") +global.Get("document").Set("body", jsObject(global.Get("document").Call("createElement", "body"))) +global.Call("requestAnimationFrame", func(float64)) +(expect body to be set now) +global.Get("document").Call("createElement", "body").Get("classList") +global.Get("document").Call("createElement", "body").Get("dataset") +global.Get("document").Call("createElement", "body").Get("style") +global.Get("document").Call("createElement", "body").Get("classList") +global.Get("document").Call("createElement", "body").Get("dataset") +global.Get("document").Call("createElement", "body").Get("style") +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "body").Call("appendChild", jsObject(global.Get("document").Call("createElement", "div"))) +global.Call("requestAnimationFrame", func(float64)) \ No newline at end of file diff --git a/testdata/TestRerender_change__new_child.want.txt b/testdata/TestRerender_change__new_child.want.txt new file mode 100755 index 00000000..d8adc033 --- /dev/null +++ b/testdata/TestRerender_change__new_child.want.txt @@ -0,0 +1,23 @@ +global.Get("document") +global.Get("document").Call("createElement", "body") +global.Get("document").Call("createElement", "body").Get("classList") +global.Get("document").Call("createElement", "body").Get("dataset") +global.Get("document").Call("createElement", "body").Get("style") +global.Get("document") +global.Get("document").Get("readyState") +global.Get("document").Set("body", jsObject(global.Get("document").Call("createElement", "body"))) +global.Call("requestAnimationFrame", func(float64)) +(expect body to be set now) +global.Get("document").Call("createElement", "body").Get("classList") +global.Get("document").Call("createElement", "body").Get("dataset") +global.Get("document").Call("createElement", "body").Get("style") +global.Get("document").Call("createElement", "body").Get("classList") +global.Get("document").Call("createElement", "body").Get("dataset") +global.Get("document").Call("createElement", "body").Get("style") +global.Get("document") +global.Get("document").Call("createElement", "div") +global.Get("document").Call("createElement", "div").Get("classList") +global.Get("document").Call("createElement", "div").Get("dataset") +global.Get("document").Call("createElement", "div").Get("style") +global.Get("document").Call("createElement", "body").Call("appendChild", jsObject(global.Get("document").Call("createElement", "div"))) +global.Call("requestAnimationFrame", func(float64)) \ No newline at end of file diff --git a/testdata/TestRerender_identical.want.txt b/testdata/TestRerender_identical.want.txt new file mode 100755 index 00000000..642de385 --- /dev/null +++ b/testdata/TestRerender_identical.want.txt @@ -0,0 +1,16 @@ +global.Get("document") +global.Get("document").Call("createElement", "body") +global.Get("document").Call("createElement", "body").Get("classList") +global.Get("document").Call("createElement", "body").Get("dataset") +global.Get("document").Call("createElement", "body").Get("style") +global.Get("document") +global.Get("document").Get("readyState") +global.Get("document").Set("body", jsObject(global.Get("document").Call("createElement", "body"))) +global.Call("requestAnimationFrame", func(float64)) +global.Get("document").Call("createElement", "body").Get("classList") +global.Get("document").Call("createElement", "body").Get("dataset") +global.Get("document").Call("createElement", "body").Get("style") +global.Get("document").Call("createElement", "body").Get("classList") +global.Get("document").Call("createElement", "body").Get("dataset") +global.Get("document").Call("createElement", "body").Get("style") +global.Call("requestAnimationFrame", func(float64)) \ No newline at end of file diff --git a/testdata/TestRerender_no_prevRender.want.txt b/testdata/TestRerender_no_prevRender.want.txt new file mode 100644 index 00000000..e69de29b diff --git a/testdata/TestSetTitle.want.txt b/testdata/TestSetTitle.want.txt new file mode 100755 index 00000000..cfa05491 --- /dev/null +++ b/testdata/TestSetTitle.want.txt @@ -0,0 +1,2 @@ +global.Get("document") +global.Get("document").Set("title", "foobartitle") \ No newline at end of file diff --git a/testsuite_test.go b/testsuite_test.go new file mode 100644 index 00000000..daee16f0 --- /dev/null +++ b/testsuite_test.go @@ -0,0 +1,277 @@ +package vecty + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path" + "path/filepath" + "reflect" + "sort" + "strings" + "testing" +) + +var _ = func() bool { + isTest = true + return true +}() + +// recoverStr runs f and returns the recovered panic as a string. +func recoverStr(f func()) (s string) { + defer func() { + s = fmt.Sprint(recover()) + }() + f() + return +} + +type componentFunc struct { + Core + id string + render func() ComponentOrHTML + skipRender func(prev Component) bool +} + +func (c *componentFunc) Render() ComponentOrHTML { return c.render() } +func (c *componentFunc) SkipRender(prev Component) bool { return c.skipRender(prev) } + +func TestMain(m *testing.M) { + // Try to remove all testdata/*.got.txt files now. + matches, _ := filepath.Glob("testdata/*.got.txt") + for _, match := range matches { + os.Remove(match) + } + + os.Exit(m.Run()) +} + +func testSuite(t *testing.T, testName string) *testSuiteT { + ts := &testSuiteT{ + t: t, + testName: testName, + callbacks: make(map[string]interface{}), + strings: &valueMocker{}, + bools: &valueMocker{}, + floats: &valueMocker{}, + ints: &valueMocker{}, + } + global = &objectRecorder{ + ts: ts, + name: "global", + } + return ts +} + +// mockedValue represents a mocked value. +type mockedValue struct { + invocation string + value interface{} +} + +// valueMocker keeps tracked of mocked values for method invocations on +// jsObject's. +type valueMocker struct { + values []mockedValue +} + +// mock adds an entry to mock the specified invocation to return the given +// value. +func (v *valueMocker) mock(invocation string, value interface{}) { + v.values = append(v.values, mockedValue{invocation, value}) +} + +// get gets the mocked value for the specified invocation. +func (v *valueMocker) get(invocation string) interface{} { + for i, value := range v.values { + if value.invocation == invocation { + // Found the right invocation. + v.values = append(v.values[:i], v.values[i+1:]...) + return value.value + } + } + panic(fmt.Sprintf("expected mocked value for invocation: %s", invocation)) +} + +type testSuiteT struct { + t *testing.T + testName string + callbacks map[string]interface{} + strings, bools, floats, ints *valueMocker + + got string + isDone bool +} + +func (ts *testSuiteT) done() { + ts.multiSortedDone() +} + +// sortedDone is just like done(), except it sorts the specified line range first. +func (ts *testSuiteT) sortedDone(sortStartLine, sortEndLine int) { + ts.multiSortedDone([2]int{sortStartLine, sortEndLine}) +} + +// multiSortedDone is just like done(), except it sorts the specified line range first. +func (ts *testSuiteT) multiSortedDone(linesToSort ...[2]int) { + if ts.isDone { + panic("testSuite done methods called multiple times") + } + ts.isDone = true + // Read the want file or create it if it does not exist. + wantFileName := path.Join("testdata", ts.testName+".want.txt") + wantBytes, err := ioutil.ReadFile(wantFileName) + if err != nil { + if os.IsNotExist(err) { + // Touch the file + f, err := os.Create(wantFileName) + f.Close() + if err != nil { + ts.t.Fatal(err) + } + } else { + ts.t.Fatal(err) + } + } + want := strings.TrimSpace(string(wantBytes)) + + // Ensure output is properly sorted. + split := strings.Split(strings.TrimSpace(ts.got), "\n") + for _, pair := range linesToSort { + sortStartLine := pair[0] - 1 // to match editor line numbers + if sortStartLine < 0 { + sortStartLine = 0 + } + sortEndLine := pair[1] + if sortEndLine > len(split) { + sortEndLine = len(split) + } + sorted := split[sortStartLine:sortEndLine] + ts.t.Logf("lines selected for sorting (%d-%d):\n%s\n\n", sortStartLine, sortEndLine, strings.Join(sorted, "\n")) + sort.Strings(sorted) + for i := sortStartLine; i < sortEndLine; i++ { + split[i] = sorted[i-sortStartLine] + } + } + got := strings.Join(split, "\n") + + // Check if we got what we wanted. + if got == want { + // Successful test. + + // Ensure there are no unused mocked values. + for _, v := range ts.strings.values { + ts.t.Errorf("unused mocked string value %q %v", v.invocation, v.value) + } + for _, v := range ts.bools.values { + ts.t.Errorf("unused mocked bool value %q %v", v.invocation, v.value) + } + for _, v := range ts.floats.values { + ts.t.Errorf("unused mocked float value %q %v", v.invocation, v.value) + } + for _, v := range ts.ints.values { + ts.t.Errorf("unused mocked int value %q %v", v.invocation, v.value) + } + return + } + + // Write what we got to disk. + gotFileName := path.Join("testdata", ts.testName+".got.txt") + err = ioutil.WriteFile(gotFileName, []byte(got), 0777) + if err != nil { + ts.t.Fatal(err) + } + + // Print a nice diff for easy comparison. + cmd := exec.Command("git", "-c", "color.ui=always", "diff", "--no-index", wantFileName, gotFileName) + out, _ := cmd.CombinedOutput() + ts.t.Log("\n" + string(out)) + + ts.t.Fatalf("to accept these changes:\n\n$ mv %s %s", gotFileName, wantFileName) +} + +// record records the invocation to the test suite and returns the string +// unmodified. +func (ts *testSuiteT) record(invocation string) string { + ts.got += "\n" + invocation + return invocation +} + +// addCallbacks adds the first function in args to ts.callbacks[invocation], if there is one. +func (ts *testSuiteT) addCallbacks(invocation string, args ...interface{}) { + for _, a := range args { + if reflect.TypeOf(a).Kind() == reflect.Func { + ts.callbacks[invocation] = a + return + } + } +} + +// objectRecorder implements the jsObject interface by recording method +// invocations to the test suite. +type objectRecorder struct { + ts *testSuiteT + name string +} + +// Set implements the jsObject interface. +func (r *objectRecorder) Set(key string, value interface{}) { + invocation := r.ts.record(fmt.Sprintf("%s.Set(%q, %+v)", r.name, key, stringify(value))) + r.ts.addCallbacks(invocation, value) +} + +// Get implements the jsObject interface. +func (r *objectRecorder) Get(key string) jsObject { + invocation := r.ts.record(fmt.Sprintf("%s.Get(%q)", r.name, key)) + return &objectRecorder{ + ts: r.ts, + name: invocation, + } +} + +// Delete implements the jsObject interface. +func (r *objectRecorder) Delete(key string) { + r.ts.record(fmt.Sprintf("%s.Delete(%q)", r.name, key)) +} + +// Call implements the jsObject interface. +func (r *objectRecorder) Call(name string, args ...interface{}) jsObject { + invocation := r.ts.record(fmt.Sprintf("%s.Call(%q, %s)", r.name, name, stringify(args...))) + r.ts.addCallbacks(invocation, args...) + return &objectRecorder{ + ts: r.ts, + name: invocation, + } +} + +// String implements the jsObject interface. +func (r *objectRecorder) String() string { return r.ts.strings.get(r.name).(string) } + +// Bool implements the jsObject interface. +func (r *objectRecorder) Bool() bool { return r.ts.bools.get(r.name).(bool) } + +// Int implements the jsObject interface. +func (r *objectRecorder) Int() int { return r.ts.ints.get(r.name).(int) } + +// Float implements the jsObject interface. +func (r *objectRecorder) Float() float64 { return r.ts.floats.get(r.name).(float64) } + +func stringify(args ...interface{}) string { + var s []string + for _, a := range args { + if reflect.TypeOf(a).Kind() == reflect.Func { + s = append(s, reflect.TypeOf(a).String()) + continue + } + switch v := a.(type) { + case string: + s = append(s, fmt.Sprintf("%q", v)) + case *objectRecorder: + s = append(s, fmt.Sprintf("jsObject(%s)", v.name)) + default: + s = append(s, fmt.Sprintf("%v", v)) + } + } + return strings.Join(s, ", ") +}