~repos /gromer
git clone https://pyrossh.dev/repos/gromer.git
gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.
e218d833
—
pyros2097 4 years ago
reduce more deps
- app_nowasm.go +1 -2
- app_wasm.go +1 -1
- js/js.go +7 -2
- js/js_nowasm.go +0 -12
- js/js_wasm.go +13 -18
- log.go +0 -48
- router.go +5 -28
- storage_wasm.go +1 -16
- testing.go +268 -268
- testing_test.go +38 -38
- utils.go +16 -0
app_nowasm.go
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
package app
|
|
4
4
|
|
|
5
5
|
import (
|
|
6
|
-
"fmt"
|
|
7
6
|
"io"
|
|
8
7
|
"net/http"
|
|
9
8
|
"os"
|
|
@@ -19,7 +18,7 @@ func Run() {
|
|
|
19
18
|
if !isLambda {
|
|
20
19
|
wd, err := os.Getwd()
|
|
21
20
|
if err != nil {
|
|
22
|
-
|
|
21
|
+
println("could not get wd")
|
|
23
22
|
return
|
|
24
23
|
}
|
|
25
24
|
assetsFS := http.FileServer(pkger.Dir(filepath.Join(wd, "assets")))
|
app_wasm.go
CHANGED
|
@@ -8,7 +8,7 @@ import (
|
|
|
8
8
|
)
|
|
9
9
|
|
|
10
10
|
func Run() {
|
|
11
|
-
handle, _, _ := AppRouter.Lookup("GET", js.Window.
|
|
11
|
+
handle, _, _ := AppRouter.Lookup("GET", js.Window.Location().Pathname)
|
|
12
12
|
if handle == nil {
|
|
13
13
|
renderFunc = AppRouter.NotFound
|
|
14
14
|
} else {
|
js/js.go
CHANGED
|
@@ -2,7 +2,6 @@ package js
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"fmt"
|
|
5
|
-
"net/url"
|
|
6
5
|
)
|
|
7
6
|
|
|
8
7
|
// Type represents the JavaScript type of a Value.
|
|
@@ -175,7 +174,7 @@ type BrowserWindow interface {
|
|
|
175
174
|
Value
|
|
176
175
|
|
|
177
176
|
// The window current url (window.location.href).
|
|
178
|
-
URL()
|
|
177
|
+
URL() string
|
|
179
178
|
|
|
180
179
|
// The window size.
|
|
181
180
|
Size() (w, h int)
|
|
@@ -250,3 +249,9 @@ func (h EventHandler) Equal(o EventHandler) bool {
|
|
|
250
249
|
return h.Event == o.Event &&
|
|
251
250
|
fmt.Sprintf("%p", h.Value) == fmt.Sprintf("%p", o.Value)
|
|
252
251
|
}
|
|
252
|
+
|
|
253
|
+
type Location struct {
|
|
254
|
+
value Value
|
|
255
|
+
Href string
|
|
256
|
+
Pathname string
|
|
257
|
+
}
|
js/js_nowasm.go
CHANGED
|
@@ -2,10 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
package js
|
|
4
4
|
|
|
5
|
-
import (
|
|
6
|
-
"net/url"
|
|
7
|
-
)
|
|
8
|
-
|
|
9
5
|
var Window = &browserWindow{value: value{}}
|
|
10
6
|
|
|
11
7
|
type value struct{}
|
|
@@ -106,10 +102,6 @@ type browserWindow struct {
|
|
|
106
102
|
value
|
|
107
103
|
}
|
|
108
104
|
|
|
109
|
-
func (w browserWindow) URL() *url.URL {
|
|
110
|
-
panic("wasm required")
|
|
111
|
-
}
|
|
112
|
-
|
|
113
105
|
func (w browserWindow) Size() (width, height int) {
|
|
114
106
|
panic("wasm required")
|
|
115
107
|
}
|
|
@@ -146,10 +138,6 @@ func copyBytesToJS(dst Value, src []byte) int {
|
|
|
146
138
|
panic("wasm required")
|
|
147
139
|
}
|
|
148
140
|
|
|
149
|
-
type Location struct {
|
|
150
|
-
value
|
|
151
|
-
}
|
|
152
|
-
|
|
153
141
|
func (l *Location) Reload() {
|
|
154
142
|
panic("wasm required")
|
|
155
143
|
}
|
js/js_wasm.go
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
package js
|
|
3
3
|
|
|
4
4
|
import (
|
|
5
|
-
"net/url"
|
|
6
|
-
"reflect"
|
|
7
5
|
"syscall/js"
|
|
8
6
|
)
|
|
9
7
|
|
|
@@ -112,16 +110,6 @@ type browserWindow struct {
|
|
|
112
110
|
cursorY int
|
|
113
111
|
}
|
|
114
112
|
|
|
115
|
-
func (w *browserWindow) URL() *url.URL {
|
|
116
|
-
rawurl := w.
|
|
117
|
-
Get("location").
|
|
118
|
-
Get("href").
|
|
119
|
-
String()
|
|
120
|
-
|
|
121
|
-
u, _ := url.Parse(rawurl)
|
|
122
|
-
return u
|
|
123
|
-
}
|
|
124
|
-
|
|
125
113
|
func (w *browserWindow) Size() (width int, height int) {
|
|
126
114
|
getSize := func(axis string) int {
|
|
127
115
|
size := w.Get("inner" + axis)
|
|
@@ -177,11 +165,17 @@ func (w *browserWindow) AddEventListener(event string, h EventHandler) func() {
|
|
|
177
165
|
}
|
|
178
166
|
|
|
179
167
|
func (w *browserWindow) Location() *Location {
|
|
168
|
+
return &Location{
|
|
180
|
-
|
|
169
|
+
value: w.Get("location"),
|
|
170
|
+
Href: w.
|
|
171
|
+
Get("location").
|
|
172
|
+
Get("href").
|
|
173
|
+
String(),
|
|
174
|
+
Pathname: w.
|
|
175
|
+
Get("location").
|
|
176
|
+
Get("pathname").
|
|
177
|
+
String(),
|
|
181
|
-
}
|
|
178
|
+
}
|
|
182
|
-
|
|
183
|
-
type Location struct {
|
|
184
|
-
value Value
|
|
185
179
|
}
|
|
186
180
|
|
|
187
181
|
func (l *Location) Reload() {
|
|
@@ -207,7 +201,8 @@ func jsval(v Value) js.Value {
|
|
|
207
201
|
return jsval(v.Value)
|
|
208
202
|
|
|
209
203
|
default:
|
|
204
|
+
// + reflect.TypeOf(v).String()
|
|
210
|
-
println("syscall/js value conversion failed type: "
|
|
205
|
+
println("syscall/js value conversion failed type: ", v)
|
|
211
206
|
return js.Undefined()
|
|
212
207
|
}
|
|
213
208
|
}
|
log.go
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
package app
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"fmt"
|
|
5
|
-
"runtime"
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
var (
|
|
9
|
-
// DefaultLogger is the logger used to log info and errors.
|
|
10
|
-
DefaultLogger func(format string, v ...interface{}) = log
|
|
11
|
-
|
|
12
|
-
defaultColor string
|
|
13
|
-
errorColor string
|
|
14
|
-
infoColor string
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
func init() {
|
|
18
|
-
switch runtime.GOARCH {
|
|
19
|
-
case "wasm", "window":
|
|
20
|
-
default:
|
|
21
|
-
defaultColor = "\033[00m"
|
|
22
|
-
errorColor = "\033[91m"
|
|
23
|
-
infoColor = "\033[94m"
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Log logs according to a format specifier using the default logger.
|
|
28
|
-
func Log(format string, v ...interface{}) {
|
|
29
|
-
DefaultLogger(format, v...)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
func log(format string, v ...interface{}) {
|
|
33
|
-
errorLevel := false
|
|
34
|
-
|
|
35
|
-
for _, a := range v {
|
|
36
|
-
if _, ok := a.(error); ok {
|
|
37
|
-
errorLevel = true
|
|
38
|
-
break
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if errorLevel {
|
|
43
|
-
fmt.Printf(errorColor+"ERROR ‣ "+defaultColor+format+"\n", v...)
|
|
44
|
-
return
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
fmt.Printf(infoColor+"INFO ‣ "+defaultColor+format+"\n", v...)
|
|
48
|
-
}
|
router.go
CHANGED
|
@@ -5,7 +5,6 @@ import (
|
|
|
5
5
|
"context"
|
|
6
6
|
"net/http"
|
|
7
7
|
"strings"
|
|
8
|
-
"sync"
|
|
9
8
|
"unicode"
|
|
10
9
|
"unicode/utf8"
|
|
11
10
|
|
|
@@ -719,9 +718,8 @@ func ParamsFromContext(ctx context.Context) Params {
|
|
|
719
718
|
// Router is a http.Handler which can be used to dispatch requests to different
|
|
720
719
|
// handler functions via configurable routes
|
|
721
720
|
type Router struct {
|
|
722
|
-
trees
|
|
721
|
+
trees map[string]*node
|
|
723
|
-
paramsPool sync.Pool
|
|
724
|
-
maxParams
|
|
722
|
+
maxParams uint16
|
|
725
723
|
// Enables automatic redirection if the current route can't be matched but a
|
|
726
724
|
// handler for the path with (without) the trailing slash exists.
|
|
727
725
|
// For example if /foo/ is requested but a route only exists for /foo, the
|
|
@@ -763,18 +761,6 @@ var AppRouter = &Router{
|
|
|
763
761
|
},
|
|
764
762
|
}
|
|
765
763
|
|
|
766
|
-
func (r *Router) getParams() *Params {
|
|
767
|
-
ps, _ := r.paramsPool.Get().(*Params)
|
|
768
|
-
*ps = (*ps)[0:0] // reset slice
|
|
769
|
-
return ps
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
func (r *Router) putParams(ps *Params) {
|
|
773
|
-
if ps != nil {
|
|
774
|
-
r.paramsPool.Put(ps)
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
|
|
778
764
|
// GET is a shortcut for router.Handle(http.MethodGet, path, handle)
|
|
779
765
|
func (r *Router) GET(path string, handle interface{}) {
|
|
780
766
|
r.Handle(http.MethodGet, path, handle)
|
|
@@ -836,14 +822,6 @@ func (r *Router) Handle(method, path string, handle interface{}) {
|
|
|
836
822
|
if paramsCount := countParams(path); paramsCount+varsCount > r.maxParams {
|
|
837
823
|
r.maxParams = paramsCount + varsCount
|
|
838
824
|
}
|
|
839
|
-
|
|
840
|
-
// Lazy-init paramsPool alloc func
|
|
841
|
-
if r.paramsPool.New == nil && r.maxParams > 0 {
|
|
842
|
-
r.paramsPool.New = func() interface{} {
|
|
843
|
-
ps := make(Params, 0, r.maxParams)
|
|
844
|
-
return &ps
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
825
|
}
|
|
848
826
|
|
|
849
827
|
// Lookup allows the manual lookup of a method + path combo.
|
|
@@ -853,9 +831,8 @@ func (r *Router) Handle(method, path string, handle interface{}) {
|
|
|
853
831
|
// the same path with an extra / without the trailing slash should be performed.
|
|
854
832
|
func (r *Router) Lookup(method, path string) (interface{}, Params, bool) {
|
|
855
833
|
if root := r.trees[method]; root != nil {
|
|
856
|
-
handle, ps, tsr := root.getValue(path,
|
|
834
|
+
handle, ps, tsr := root.getValue(path, nil)
|
|
857
835
|
if handle == nil {
|
|
858
|
-
r.putParams(ps)
|
|
859
836
|
return nil, nil, tsr
|
|
860
837
|
}
|
|
861
838
|
if ps == nil {
|
|
@@ -891,7 +868,7 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
|
891
868
|
|
|
892
869
|
if root := r.trees[req.Method]; root != nil {
|
|
893
870
|
// TODO: use _ ps save it to context for useParam()
|
|
894
|
-
if handle, _, tsr := root.getValue(path,
|
|
871
|
+
if handle, _, tsr := root.getValue(path, nil); handle != nil {
|
|
895
872
|
if render, ok := handle.(RenderFunc); ok {
|
|
896
873
|
w.Header().Set("Content-Type", "text/html")
|
|
897
874
|
w.WriteHeader(http.StatusOK)
|
|
@@ -957,7 +934,7 @@ func (r *Router) Lambda(ctx context.Context, e events.APIGatewayV2HTTPRequest) (
|
|
|
957
934
|
println("route: " + e.RawPath)
|
|
958
935
|
path := strings.Replace(e.RawPath, "/Prod/", "/", 1)
|
|
959
936
|
if root := r.trees[e.RequestContext.HTTP.Method]; root != nil {
|
|
960
|
-
if handle, _, _ := root.getValue(path,
|
|
937
|
+
if handle, _, _ := root.getValue(path, nil); handle != nil {
|
|
961
938
|
res.Body = r.getPage(handle.(RenderFunc)(NewRenderContext()))
|
|
962
939
|
return
|
|
963
940
|
}
|
storage_wasm.go
CHANGED
|
@@ -2,7 +2,6 @@ package app
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
"encoding/json"
|
|
5
|
-
"sync"
|
|
6
5
|
|
|
7
6
|
"github.com/pyros2097/wapp/js"
|
|
8
7
|
)
|
|
@@ -13,8 +12,7 @@ func init() {
|
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
type jsStorage struct {
|
|
16
|
-
name
|
|
15
|
+
name string
|
|
17
|
-
mutex sync.RWMutex
|
|
18
16
|
}
|
|
19
17
|
|
|
20
18
|
func newJSStorage(name string) *jsStorage {
|
|
@@ -28,10 +26,6 @@ func (s *jsStorage) Set(k string, v interface{}) (err error) {
|
|
|
28
26
|
panic("setting storage value failed storage-type: " + s.name + "key " + k)
|
|
29
27
|
}
|
|
30
28
|
}()
|
|
31
|
-
|
|
32
|
-
s.mutex.Lock()
|
|
33
|
-
defer s.mutex.Unlock()
|
|
34
|
-
|
|
35
29
|
b, err := json.Marshal(v)
|
|
36
30
|
if err != nil {
|
|
37
31
|
return err
|
|
@@ -42,9 +36,6 @@ func (s *jsStorage) Set(k string, v interface{}) (err error) {
|
|
|
42
36
|
}
|
|
43
37
|
|
|
44
38
|
func (s *jsStorage) Get(k string, v interface{}) error {
|
|
45
|
-
s.mutex.RLock()
|
|
46
|
-
defer s.mutex.RUnlock()
|
|
47
|
-
|
|
48
39
|
item := js.Window.Get(s.name).Call("getItem", k)
|
|
49
40
|
if !item.Truthy() {
|
|
50
41
|
return nil
|
|
@@ -54,15 +45,9 @@ func (s *jsStorage) Get(k string, v interface{}) error {
|
|
|
54
45
|
}
|
|
55
46
|
|
|
56
47
|
func (s *jsStorage) Del(k string) {
|
|
57
|
-
s.mutex.Lock()
|
|
58
|
-
defer s.mutex.Unlock()
|
|
59
|
-
|
|
60
48
|
js.Window.Get(s.name).Call("removeItem", k)
|
|
61
49
|
}
|
|
62
50
|
|
|
63
51
|
func (s *jsStorage) Clear() {
|
|
64
|
-
s.mutex.Lock()
|
|
65
|
-
defer s.mutex.Unlock()
|
|
66
|
-
|
|
67
52
|
js.Window.Get(s.name).Call("clear")
|
|
68
53
|
}
|
testing.go
CHANGED
|
@@ -1,283 +1,283 @@
|
|
|
1
1
|
package app
|
|
2
2
|
|
|
3
|
-
import (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
// TestUIDescriptor represents a descriptor that describes a UI element and its
|
|
10
|
-
// location from its parents.
|
|
11
|
-
type TestUIDescriptor struct {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// TestPath is a helper function that returns a path to use in a
|
|
34
|
-
// TestUIDescriptor.
|
|
35
|
-
func TestPath(p ...int) []int {
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// TestMatch looks for the element targeted by the descriptor in the given tree
|
|
40
|
-
// and reports whether it matches with the expected element.
|
|
41
|
-
//
|
|
42
|
-
// Eg:
|
|
43
|
-
// tree := app.Div().Body(
|
|
44
|
-
// app.H2().Body(
|
|
45
|
-
// app.Text("foo"),
|
|
46
|
-
// ),
|
|
47
|
-
// app.P().Body(
|
|
48
|
-
// app.Text("bar"),
|
|
49
|
-
// ),
|
|
50
|
-
// )
|
|
51
|
-
//
|
|
52
|
-
// // Testing root:
|
|
53
|
-
// err := app.TestMatch(tree, app.TestUIDescriptor{
|
|
54
|
-
// Path: TestPath(),
|
|
55
|
-
// Expected: app.Div(),
|
|
56
|
-
// })
|
|
57
|
-
// // OK => err == nil
|
|
58
|
-
//
|
|
59
|
-
// // Testing h2:
|
|
60
|
-
// err := app.TestMatch(tree, app.TestUIDescriptor{
|
|
61
|
-
// Path: TestPath(0),
|
|
62
|
-
// Expected: app.H3(),
|
|
63
|
-
// })
|
|
64
|
-
// // KO => err != nil because we ask h2 to match with h3
|
|
65
|
-
//
|
|
66
|
-
// // Testing text from p:
|
|
67
|
-
// err = app.TestMatch(tree, app.TestUIDescriptor{
|
|
68
|
-
// Path: TestPath(1, 0),
|
|
69
|
-
// Expected: app.Text("bar"),
|
|
70
|
-
// })
|
|
71
|
-
// // OK => err == nil
|
|
72
|
-
func TestMatch(tree UI, d TestUIDescriptor) error {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
c := tree.children()[idx]
|
|
100
|
-
p := c.parent()
|
|
101
|
-
|
|
102
|
-
if p != tree {
|
|
103
|
-
return errors.New("unexpected ui element parent").
|
|
104
|
-
Tag("name", d.Expected.name()).
|
|
105
|
-
Tag("parent-name", p.name()).
|
|
106
|
-
Tag("parent-addr", fmt.Sprintf("%p", p)).
|
|
107
|
-
Tag("expected-parent-name", tree.name()).
|
|
108
|
-
Tag("expected-parent-addr", fmt.Sprintf("%p", tree))
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
d.Path = d.Path[1:]
|
|
112
|
-
return TestMatch(c, d)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if d.Expected.name() != tree.name() {
|
|
116
|
-
return errors.New("the UI element is not matching the descriptor").
|
|
117
|
-
Tag("expected-name", d.Expected.name()).
|
|
118
|
-
Tag("current-name", tree.name())
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// switch d.Expected.Kind() {
|
|
122
|
-
// case SimpleText:
|
|
123
|
-
// return matchText(tree, d)
|
|
124
|
-
|
|
125
|
-
// case HTML:
|
|
126
|
-
// if err := matchHTMLElemAttrs(tree, d); err != nil {
|
|
127
|
-
// return err
|
|
128
|
-
// }
|
|
129
|
-
// return matchHTMLElemEventHandlers(tree, d)
|
|
130
|
-
|
|
131
|
-
// // case Component:
|
|
132
|
-
// // return matchComponent(tree, d)
|
|
133
|
-
|
|
134
|
-
// case RawHTML:
|
|
135
|
-
// return matchRaw(tree, d)
|
|
136
|
-
|
|
137
|
-
// default:
|
|
138
|
-
// return errors.New("the UI element is not matching the descriptor").
|
|
139
|
-
// Tag("reason", "unavailable matching for the kind").
|
|
140
|
-
// Tag("kind", d.Expected.Kind())
|
|
141
|
-
// }
|
|
142
|
-
return nil
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
func matchText(n UI, d TestUIDescriptor) error {
|
|
146
|
-
a := n.(*text)
|
|
147
|
-
b := d.Expected.(*text)
|
|
148
|
-
|
|
149
|
-
if a.value != b.value {
|
|
150
|
-
return errors.New("the text element is not matching the descriptor").
|
|
151
|
-
Tag("name", a.name()).
|
|
152
|
-
Tag("reason", "unexpected text value").
|
|
153
|
-
Tag("expected-value", b.value).
|
|
154
|
-
Tag("current-value", a.value)
|
|
155
|
-
}
|
|
156
|
-
return nil
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
func matchHTMLElemAttrs(n UI, d TestUIDescriptor) error {
|
|
160
|
-
aAttrs := n.attributes()
|
|
161
|
-
bAttrs := d.Expected.attributes()
|
|
162
|
-
|
|
163
|
-
if len(aAttrs) != len(bAttrs) {
|
|
164
|
-
return errors.New("the html element is not matching the descriptor").
|
|
165
|
-
Tag("name", n.name()).
|
|
166
|
-
Tag("reason", "unexpected attributes length").
|
|
167
|
-
Tag("expected-attributes-length", len(bAttrs)).
|
|
168
|
-
Tag("current-attributes-length", len(aAttrs))
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
for k, b := range bAttrs {
|
|
172
|
-
a, exists := aAttrs[k]
|
|
173
|
-
if !exists {
|
|
174
|
-
return errors.New("the html element is not matching the descriptor").
|
|
175
|
-
Tag("name", n.name()).
|
|
176
|
-
Tag("reason", "an attribute is missing").
|
|
177
|
-
Tag("attribute", k)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if a != b {
|
|
181
|
-
return errors.New("the html element is not matching the descriptor").
|
|
182
|
-
Tag("name", n.name()).
|
|
183
|
-
Tag("reason", "unexpected attribute value").
|
|
184
|
-
Tag("attribute", k).
|
|
185
|
-
Tag("expected-value", b).
|
|
186
|
-
Tag("current-value", a)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
for k := range bAttrs {
|
|
191
|
-
_, exists := bAttrs[k]
|
|
192
|
-
if !exists {
|
|
193
|
-
return errors.New("the html element is not matching the descriptor").
|
|
194
|
-
Tag("name", n.name()).
|
|
195
|
-
Tag("reason", "an unexpected attribute is present").
|
|
196
|
-
Tag("attribute", k)
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return nil
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
func matchHTMLElemEventHandlers(n UI, d TestUIDescriptor) error {
|
|
204
|
-
aevents := n.eventHandlers()
|
|
205
|
-
bevents := d.Expected.eventHandlers()
|
|
206
|
-
|
|
207
|
-
if len(aevents) != len(bevents) {
|
|
208
|
-
return errors.New("the html element is not matching the descriptor").
|
|
209
|
-
Tag("name", n.name()).
|
|
210
|
-
Tag("reason", "unexpected event handlers length").
|
|
211
|
-
Tag("expected-event-handlers-length", len(bevents)).
|
|
212
|
-
Tag("current-event-handlers-length", len(aevents))
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
for k := range bevents {
|
|
216
|
-
_, exists := aevents[k]
|
|
217
|
-
if !exists {
|
|
218
|
-
return errors.New("the html element is not matching the descriptor").
|
|
219
|
-
Tag("name", n.name()).
|
|
220
|
-
Tag("reason", "an event handler is missing").
|
|
221
|
-
Tag("event-handler", k)
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
for k := range bevents {
|
|
226
|
-
_, exists := aevents[k]
|
|
227
|
-
if !exists {
|
|
228
|
-
return errors.New("the html element is not matching the descriptor").
|
|
229
|
-
Tag("name", n.name()).
|
|
230
|
-
Tag("reason", "an unexpected event handler is present").
|
|
231
|
-
Tag("event-handler", k)
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return nil
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// func matchComponent(n UI, d TestUIDescriptor) error {
|
|
240
|
-
// aval := reflect.ValueOf(n).Elem()
|
|
241
|
-
// bval := reflect.ValueOf(d.Expected).Elem()
|
|
242
|
-
|
|
243
|
-
// compotype := reflect.TypeOf(Compo{})
|
|
244
|
-
|
|
245
|
-
// for i := 0; i < bval.NumField(); i++ {
|
|
246
|
-
// a := aval.Field(i)
|
|
247
|
-
// b := bval.Field(i)
|
|
248
|
-
|
|
249
|
-
// if a.Type() == compotype {
|
|
250
|
-
// continue
|
|
3
|
+
// import (
|
|
4
|
+
// "fmt"
|
|
5
|
+
|
|
6
|
+
// "github.com/pyros2097/wapp/errors"
|
|
7
|
+
// )
|
|
8
|
+
|
|
9
|
+
// // TestUIDescriptor represents a descriptor that describes a UI element and its
|
|
10
|
+
// // location from its parents.
|
|
11
|
+
// type TestUIDescriptor struct {
|
|
12
|
+
// // The location of the node. It is used by the TestMatch to find the
|
|
13
|
+
// // element to test.
|
|
14
|
+
// //
|
|
15
|
+
// // If empty, the expected UI element is compared with the root of the tree.
|
|
16
|
+
// //
|
|
17
|
+
// // Otherwise, each integer represents the index of the element to traverse,
|
|
18
|
+
// // from the root's children to the element to compare
|
|
19
|
+
// Path []int
|
|
20
|
+
|
|
21
|
+
// // The element to compare with the element targeted by Path. Compare
|
|
22
|
+
// // behavior varies depending on the element kind.
|
|
23
|
+
// //
|
|
24
|
+
// // Simple text elements only have their text value compared.
|
|
25
|
+
// //
|
|
26
|
+
// // HTML elements have their attribute compared and check if their event
|
|
27
|
+
// // handlers are set.
|
|
28
|
+
// //
|
|
29
|
+
// // Components have their exported field values compared.
|
|
30
|
+
// Expected UI
|
|
31
|
+
// }
|
|
32
|
+
|
|
33
|
+
// // TestPath is a helper function that returns a path to use in a
|
|
34
|
+
// // TestUIDescriptor.
|
|
35
|
+
// func TestPath(p ...int) []int {
|
|
36
|
+
// return p
|
|
37
|
+
// }
|
|
38
|
+
|
|
39
|
+
// // TestMatch looks for the element targeted by the descriptor in the given tree
|
|
40
|
+
// // and reports whether it matches with the expected element.
|
|
41
|
+
// //
|
|
42
|
+
// // Eg:
|
|
43
|
+
// // tree := app.Div().Body(
|
|
44
|
+
// // app.H2().Body(
|
|
45
|
+
// // app.Text("foo"),
|
|
46
|
+
// // ),
|
|
47
|
+
// // app.P().Body(
|
|
48
|
+
// // app.Text("bar"),
|
|
49
|
+
// // ),
|
|
50
|
+
// // )
|
|
51
|
+
// //
|
|
52
|
+
// // // Testing root:
|
|
53
|
+
// // err := app.TestMatch(tree, app.TestUIDescriptor{
|
|
54
|
+
// // Path: TestPath(),
|
|
55
|
+
// // Expected: app.Div(),
|
|
56
|
+
// // })
|
|
57
|
+
// // // OK => err == nil
|
|
58
|
+
// //
|
|
59
|
+
// // // Testing h2:
|
|
60
|
+
// // err := app.TestMatch(tree, app.TestUIDescriptor{
|
|
61
|
+
// // Path: TestPath(0),
|
|
62
|
+
// // Expected: app.H3(),
|
|
63
|
+
// // })
|
|
64
|
+
// // // KO => err != nil because we ask h2 to match with h3
|
|
65
|
+
// //
|
|
66
|
+
// // // Testing text from p:
|
|
67
|
+
// // err = app.TestMatch(tree, app.TestUIDescriptor{
|
|
68
|
+
// // Path: TestPath(1, 0),
|
|
69
|
+
// // Expected: app.Text("bar"),
|
|
70
|
+
// // })
|
|
71
|
+
// // // OK => err == nil
|
|
72
|
+
// func TestMatch(tree UI, d TestUIDescriptor) error {
|
|
73
|
+
// if !tree.Mounted() {
|
|
74
|
+
// if err := mount(tree); err != nil {
|
|
75
|
+
// return err
|
|
76
|
+
// }
|
|
77
|
+
// }
|
|
78
|
+
|
|
79
|
+
// if d.Expected != nil {
|
|
80
|
+
// d.Expected.setSelf(d.Expected)
|
|
81
|
+
// }
|
|
82
|
+
|
|
83
|
+
// if len(d.Path) != 0 {
|
|
84
|
+
// idx := d.Path[0]
|
|
85
|
+
|
|
86
|
+
// if idx < 0 || idx >= len(tree.children()) {
|
|
87
|
+
// // Check that the element does not exists.
|
|
88
|
+
// if d.Expected == nil {
|
|
89
|
+
// return nil
|
|
90
|
+
// }
|
|
91
|
+
|
|
92
|
+
// return errors.New("ui element to match is out of range").
|
|
93
|
+
// Tag("name", d.Expected.name()).
|
|
94
|
+
// Tag("parent-name", tree.name()).
|
|
95
|
+
// Tag("parent-children-count", len(tree.children())).
|
|
96
|
+
// Tag("index", idx)
|
|
251
97
|
// }
|
|
252
98
|
|
|
99
|
+
// c := tree.children()[idx]
|
|
100
|
+
// p := c.parent()
|
|
101
|
+
|
|
253
|
-
// if !
|
|
102
|
+
// if p != tree {
|
|
103
|
+
// return errors.New("unexpected ui element parent").
|
|
104
|
+
// Tag("name", d.Expected.name()).
|
|
254
|
-
//
|
|
105
|
+
// Tag("parent-name", p.name()).
|
|
106
|
+
// Tag("parent-addr", fmt.Sprintf("%p", p)).
|
|
107
|
+
// Tag("expected-parent-name", tree.name()).
|
|
108
|
+
// Tag("expected-parent-addr", fmt.Sprintf("%p", tree))
|
|
255
109
|
// }
|
|
256
110
|
|
|
111
|
+
// d.Path = d.Path[1:]
|
|
112
|
+
// return TestMatch(c, d)
|
|
113
|
+
// }
|
|
114
|
+
|
|
257
|
-
//
|
|
115
|
+
// if d.Expected.name() != tree.name() {
|
|
116
|
+
// return errors.New("the UI element is not matching the descriptor").
|
|
117
|
+
// Tag("expected-name", d.Expected.name()).
|
|
118
|
+
// Tag("current-name", tree.name())
|
|
119
|
+
// }
|
|
120
|
+
|
|
121
|
+
// // switch d.Expected.Kind() {
|
|
122
|
+
// // case SimpleText:
|
|
123
|
+
// // return matchText(tree, d)
|
|
124
|
+
|
|
125
|
+
// // case HTML:
|
|
126
|
+
// // if err := matchHTMLElemAttrs(tree, d); err != nil {
|
|
127
|
+
// // return err
|
|
128
|
+
// // }
|
|
129
|
+
// // return matchHTMLElemEventHandlers(tree, d)
|
|
130
|
+
|
|
131
|
+
// // // case Component:
|
|
132
|
+
// // // return matchComponent(tree, d)
|
|
133
|
+
|
|
134
|
+
// // case RawHTML:
|
|
135
|
+
// // return matchRaw(tree, d)
|
|
136
|
+
|
|
137
|
+
// // default:
|
|
138
|
+
// // return errors.New("the UI element is not matching the descriptor").
|
|
139
|
+
// // Tag("reason", "unavailable matching for the kind").
|
|
140
|
+
// // Tag("kind", d.Expected.Kind())
|
|
141
|
+
// // }
|
|
142
|
+
// return nil
|
|
143
|
+
// }
|
|
144
|
+
|
|
145
|
+
// func matchText(n UI, d TestUIDescriptor) error {
|
|
146
|
+
// a := n.(*text)
|
|
147
|
+
// b := d.Expected.(*text)
|
|
148
|
+
|
|
149
|
+
// if a.value != b.value {
|
|
150
|
+
// return errors.New("the text element is not matching the descriptor").
|
|
151
|
+
// Tag("name", a.name()).
|
|
152
|
+
// Tag("reason", "unexpected text value").
|
|
153
|
+
// Tag("expected-value", b.value).
|
|
154
|
+
// Tag("current-value", a.value)
|
|
155
|
+
// }
|
|
156
|
+
// return nil
|
|
157
|
+
// }
|
|
158
|
+
|
|
159
|
+
// func matchHTMLElemAttrs(n UI, d TestUIDescriptor) error {
|
|
160
|
+
// aAttrs := n.attributes()
|
|
161
|
+
// bAttrs := d.Expected.attributes()
|
|
162
|
+
|
|
163
|
+
// if len(aAttrs) != len(bAttrs) {
|
|
164
|
+
// return errors.New("the html element is not matching the descriptor").
|
|
165
|
+
// Tag("name", n.name()).
|
|
166
|
+
// Tag("reason", "unexpected attributes length").
|
|
167
|
+
// Tag("expected-attributes-length", len(bAttrs)).
|
|
168
|
+
// Tag("current-attributes-length", len(aAttrs))
|
|
169
|
+
// }
|
|
170
|
+
|
|
171
|
+
// for k, b := range bAttrs {
|
|
172
|
+
// a, exists := aAttrs[k]
|
|
173
|
+
// if !exists {
|
|
258
|
-
// return errors.New("the
|
|
174
|
+
// return errors.New("the html element is not matching the descriptor").
|
|
175
|
+
// Tag("name", n.name()).
|
|
176
|
+
// Tag("reason", "an attribute is missing").
|
|
177
|
+
// Tag("attribute", k)
|
|
178
|
+
// }
|
|
179
|
+
|
|
180
|
+
// if a != b {
|
|
181
|
+
// return errors.New("the html element is not matching the descriptor").
|
|
182
|
+
// Tag("name", n.name()).
|
|
183
|
+
// Tag("reason", "unexpected attribute value").
|
|
184
|
+
// Tag("attribute", k).
|
|
185
|
+
// Tag("expected-value", b).
|
|
186
|
+
// Tag("current-value", a)
|
|
187
|
+
// }
|
|
188
|
+
// }
|
|
189
|
+
|
|
190
|
+
// for k := range bAttrs {
|
|
191
|
+
// _, exists := bAttrs[k]
|
|
192
|
+
// if !exists {
|
|
193
|
+
// return errors.New("the html element is not matching the descriptor").
|
|
259
194
|
// Tag("name", n.name()).
|
|
260
|
-
// Tag("reason", "unexpected
|
|
195
|
+
// Tag("reason", "an unexpected attribute is present").
|
|
261
|
-
// Tag("field", bval.Type().Field(i).Name).
|
|
262
|
-
// Tag("expected-value", b.Interface()).
|
|
263
|
-
// Tag("
|
|
196
|
+
// Tag("attribute", k)
|
|
264
197
|
// }
|
|
265
198
|
// }
|
|
266
199
|
|
|
267
200
|
// return nil
|
|
268
201
|
// }
|
|
269
202
|
|
|
270
|
-
func
|
|
203
|
+
// func matchHTMLElemEventHandlers(n UI, d TestUIDescriptor) error {
|
|
271
|
-
|
|
204
|
+
// aevents := n.eventHandlers()
|
|
272
|
-
|
|
205
|
+
// bevents := d.Expected.eventHandlers()
|
|
273
206
|
|
|
274
|
-
|
|
207
|
+
// if len(aevents) != len(bevents) {
|
|
275
|
-
|
|
208
|
+
// return errors.New("the html element is not matching the descriptor").
|
|
276
|
-
|
|
209
|
+
// Tag("name", n.name()).
|
|
277
|
-
|
|
210
|
+
// Tag("reason", "unexpected event handlers length").
|
|
278
|
-
|
|
211
|
+
// Tag("expected-event-handlers-length", len(bevents)).
|
|
279
|
-
|
|
212
|
+
// Tag("current-event-handlers-length", len(aevents))
|
|
280
|
-
|
|
213
|
+
// }
|
|
281
214
|
|
|
215
|
+
// for k := range bevents {
|
|
216
|
+
// _, exists := aevents[k]
|
|
217
|
+
// if !exists {
|
|
218
|
+
// return errors.New("the html element is not matching the descriptor").
|
|
219
|
+
// Tag("name", n.name()).
|
|
220
|
+
// Tag("reason", "an event handler is missing").
|
|
221
|
+
// Tag("event-handler", k)
|
|
222
|
+
// }
|
|
223
|
+
// }
|
|
224
|
+
|
|
225
|
+
// for k := range bevents {
|
|
226
|
+
// _, exists := aevents[k]
|
|
227
|
+
// if !exists {
|
|
228
|
+
// return errors.New("the html element is not matching the descriptor").
|
|
229
|
+
// Tag("name", n.name()).
|
|
230
|
+
// Tag("reason", "an unexpected event handler is present").
|
|
231
|
+
// Tag("event-handler", k)
|
|
232
|
+
// }
|
|
233
|
+
// }
|
|
234
|
+
|
|
282
|
-
|
|
235
|
+
// return nil
|
|
236
|
+
|
|
283
|
-
}
|
|
237
|
+
// }
|
|
238
|
+
|
|
239
|
+
// // func matchComponent(n UI, d TestUIDescriptor) error {
|
|
240
|
+
// // aval := reflect.ValueOf(n).Elem()
|
|
241
|
+
// // bval := reflect.ValueOf(d.Expected).Elem()
|
|
242
|
+
|
|
243
|
+
// // compotype := reflect.TypeOf(Compo{})
|
|
244
|
+
|
|
245
|
+
// // for i := 0; i < bval.NumField(); i++ {
|
|
246
|
+
// // a := aval.Field(i)
|
|
247
|
+
// // b := bval.Field(i)
|
|
248
|
+
|
|
249
|
+
// // if a.Type() == compotype {
|
|
250
|
+
// // continue
|
|
251
|
+
// // }
|
|
252
|
+
|
|
253
|
+
// // if !a.CanSet() {
|
|
254
|
+
// // continue
|
|
255
|
+
// // }
|
|
256
|
+
|
|
257
|
+
// // if !reflect.DeepEqual(a.Interface(), b.Interface()) {
|
|
258
|
+
// // return errors.New("the component is not matching with the descriptor").
|
|
259
|
+
// // Tag("name", n.name()).
|
|
260
|
+
// // Tag("reason", "unexpected field value").
|
|
261
|
+
// // Tag("field", bval.Type().Field(i).Name).
|
|
262
|
+
// // Tag("expected-value", b.Interface()).
|
|
263
|
+
// // Tag("current-value", a.Interface())
|
|
264
|
+
// // }
|
|
265
|
+
// // }
|
|
266
|
+
|
|
267
|
+
// // return nil
|
|
268
|
+
// // }
|
|
269
|
+
|
|
270
|
+
// func matchRaw(n UI, d TestUIDescriptor) error {
|
|
271
|
+
// a := n.(*raw)
|
|
272
|
+
// b := d.Expected.(*raw)
|
|
273
|
+
|
|
274
|
+
// if a.value != b.value {
|
|
275
|
+
// return errors.New("the raw html element is not matching with the descriptor").
|
|
276
|
+
// Tag("name", n.name()).
|
|
277
|
+
// Tag("reason", "unexpected value").
|
|
278
|
+
// Tag("expected-value", b.value).
|
|
279
|
+
// Tag("current-value", a.value)
|
|
280
|
+
// }
|
|
281
|
+
|
|
282
|
+
// return nil
|
|
283
|
+
// }
|
testing_test.go
CHANGED
|
@@ -1,46 +1,46 @@
|
|
|
1
1
|
package app
|
|
2
2
|
|
|
3
|
-
import (
|
|
3
|
+
// import (
|
|
4
|
-
|
|
4
|
+
// "io/ioutil"
|
|
5
|
-
|
|
5
|
+
// "os"
|
|
6
|
-
|
|
6
|
+
// "runtime"
|
|
7
|
-
|
|
7
|
+
// "testing"
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// "github.com/stretchr/testify/require"
|
|
10
|
-
)
|
|
10
|
+
// )
|
|
11
11
|
|
|
12
|
-
func testSkipNonWasm(t *testing.T) {
|
|
12
|
+
// func testSkipNonWasm(t *testing.T) {
|
|
13
|
-
|
|
13
|
+
// if goarch := runtime.GOARCH; goarch != "wasm" {
|
|
14
|
-
t.Skip("skipping test")
|
|
15
|
-
|
|
14
|
+
// t.Skip("skipping test")
|
|
15
|
+
// // t.Skip(logs.New("skipping test").
|
|
16
|
-
|
|
16
|
+
// // Tag("reason", "unsupported architecture").
|
|
17
|
-
|
|
17
|
+
// // Tag("required-architecture", "wasm").
|
|
18
|
-
|
|
18
|
+
// // Tag("current-architecture", goarch),
|
|
19
|
+
// // )
|
|
20
|
+
// }
|
|
19
|
-
|
|
21
|
+
// }
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
22
|
|
|
23
|
-
func testSkipWasm(t *testing.T) {
|
|
23
|
+
// func testSkipWasm(t *testing.T) {
|
|
24
|
-
|
|
24
|
+
// if goarch := runtime.GOARCH; goarch == "wasm" {
|
|
25
|
-
t.Skip("skipping test")
|
|
26
|
-
|
|
25
|
+
// t.Skip("skipping test")
|
|
26
|
+
// // t.Skip(logs.New("skipping test").
|
|
27
|
-
|
|
27
|
+
// // Tag("reason", "unsupported architecture").
|
|
28
|
-
|
|
28
|
+
// // Tag("required-architecture", "!= than wasm").
|
|
29
|
-
|
|
29
|
+
// // Tag("current-architecture", goarch),
|
|
30
|
+
// // )
|
|
31
|
+
// }
|
|
30
|
-
|
|
32
|
+
// }
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
33
|
|
|
34
|
-
func testCreateDir(t *testing.T, path string) func() {
|
|
34
|
+
// func testCreateDir(t *testing.T, path string) func() {
|
|
35
|
-
|
|
35
|
+
// err := os.MkdirAll(path, 0755)
|
|
36
|
-
|
|
36
|
+
// require.NoError(t, err)
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
// return func() {
|
|
39
|
-
|
|
39
|
+
// os.RemoveAll(path)
|
|
40
|
+
// }
|
|
40
|
-
|
|
41
|
+
// }
|
|
41
|
-
}
|
|
42
42
|
|
|
43
|
-
func testCreateFile(t *testing.T, path, content string) {
|
|
43
|
+
// func testCreateFile(t *testing.T, path, content string) {
|
|
44
|
-
|
|
44
|
+
// err := ioutil.WriteFile(path, stob(content), 0666)
|
|
45
|
-
|
|
45
|
+
// require.NoError(t, err)
|
|
46
|
-
}
|
|
46
|
+
// }
|
utils.go
CHANGED
|
@@ -48,3 +48,19 @@ func btos(b []byte) string {
|
|
|
48
48
|
func stob(s string) []byte {
|
|
49
49
|
return *(*[]byte)(unsafe.Pointer(&s))
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
var (
|
|
53
|
+
defaultColor = "\033[00m"
|
|
54
|
+
errorColor = "\033[91m"
|
|
55
|
+
infoColor = "\033[94m"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
// Log logs according to a format specifier using the default logger.
|
|
59
|
+
func Log(format string, v ...interface{}) {
|
|
60
|
+
errorLevel := false
|
|
61
|
+
if errorLevel {
|
|
62
|
+
println(errorColor+"ERROR ‣ "+defaultColor+format+"\n", v)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
println(infoColor+"INFO ‣ "+defaultColor+format+"\n", v)
|
|
66
|
+
}
|