This repository has been archived by the owner on Jan 10, 2023. It is now read-only.
/
fake.go
324 lines (276 loc) · 8.64 KB
/
fake.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dom
import (
"math/rand"
"reflect"
"sort"
"strings"
)
// MethodFunc is what a typical method looks like... sort of.
type MethodFunc func(...interface{}) interface{}
// FakeObject implements a fake Object that sort of works like *js.Object.
type FakeObject struct {
Value interface{}
Properties map[string]interface{}
Methods map[string]MethodFunc
}
// MakeFakeObject makes a FakeObject.
func MakeFakeObject(value interface{}) *FakeObject {
if o, ok := value.(*FakeObject); ok {
return o
}
return &FakeObject{
Value: value,
Properties: make(map[string]interface{}),
Methods: make(map[string]MethodFunc),
}
}
// Get gets a "property value". In the case of fakes, this is a pointer that gets used as
// a key in ObjectsToValues.
func (o *FakeObject) Get(key string) Object {
return MakeFakeObject(o.Properties[key])
}
// Set sets a property value.
func (o *FakeObject) Set(key string, value interface{}) { o.Properties[key] = value }
// Delete deletes the property with the given key.
func (o *FakeObject) Delete(key string) { delete(o.Properties, key) }
// Length returns the length of o.Value.
func (o *FakeObject) Length() int { return reflect.ValueOf(o.Value).Len() }
// Index returns the element at index i of o.Value.
func (o *FakeObject) Index(i int) Object {
return MakeFakeObject(reflect.ValueOf(o.Value).Index(i).Interface())
}
// SetIndex sets the value at index i of o.Value to value.
func (o *FakeObject) SetIndex(i int, value interface{}) {
reflect.ValueOf(o.Value).Index(i).Set(reflect.ValueOf(value))
}
// Call calls a method. In the case of fakes, this returns a pointer that gets used as
// a key in ObjectsToValues.
func (o *FakeObject) Call(method string, args ...interface{}) Object {
return MakeFakeObject(o.Methods[method](args...))
}
// Invoke calls the function in o.Value.
func (o *FakeObject) Invoke(args ...interface{}) Object {
a2 := make([]reflect.Value, len(args))
for i, a := range args {
a2[i] = reflect.ValueOf(a)
}
return MakeFakeObject(reflect.ValueOf(o.Value).CallSlice(a2))
}
// New calls the function in o.Value.
func (o *FakeObject) New(args ...interface{}) Object { return o.Invoke(args) }
// Bool returns o.Value asserted as a bool.
func (o *FakeObject) Bool() bool { return o.Value.(bool) }
// String returns o.Value asserted as a string.
func (o *FakeObject) String() string { return o.Value.(string) }
// Int returns o.Value asserted as an int.
func (o *FakeObject) Int() int { return o.Value.(int) }
// Int64 returns o.Value asserted as an int64.
func (o *FakeObject) Int64() int64 { return o.Value.(int64) }
// Uint64 returns o.Value asserted as a uint64.
func (o *FakeObject) Uint64() uint64 { return o.Value.(uint64) }
// Float returns o.Value asserted as a float64.
func (o *FakeObject) Float() float64 { return o.Value.(float64) }
// Interface returns o.Value.
func (o *FakeObject) Interface() interface{} { return o.Value }
// Unsafe returns o.Value asserted as a uintptr.
func (o *FakeObject) Unsafe() uintptr { return o.Value.(uintptr) }
// FakeClassList implements a virtual classList (DOMTokenList).
// Unlike a real DOMTokenList, it doesn't preserve order.
type FakeClassList map[string]struct{}
// Add adds a class to the classlist.
func (c FakeClassList) Add(classes ...string) {
for _, cl := range classes {
c[cl] = struct{}{}
}
}
// Remove removes a class from the classlist.
func (c FakeClassList) Remove(classes ...string) {
for _, cl := range classes {
delete(c, cl)
}
}
// Toggle adds if the class is not present, and removes if it is.
func (c FakeClassList) Toggle(class string) {
if c.Contains(class) {
c.Remove(class)
} else {
c.Add(class)
}
}
// Contains tests if the class is present.
func (c FakeClassList) Contains(class string) bool {
_, found := c[class]
return found
}
// Replace swaps an old class for a new one.
func (c FakeClassList) Replace(oldClass, newClass string) {
c.Remove(oldClass)
c.Add(newClass)
}
func (c FakeClassList) String() string {
cls := make([]string, 0, len(c))
for cl := range c {
cls = append(cls, cl)
}
sort.Strings(cls)
return strings.Join(cls, " ")
}
// FakeElement implements a virtual DOM element.
type FakeElement struct {
FakeObject
Class string
NamespaceURI string
Attributes map[string]interface{}
Children []*FakeElement
EventListeners map[string][]func(Object)
Classes FakeClassList
parent *FakeElement
}
// MakeFakeElement makes a fake element.
func MakeFakeElement(class, nsuri string) *FakeElement {
return &FakeElement{
FakeObject: *MakeFakeObject(nil),
Class: class,
NamespaceURI: nsuri,
Attributes: make(map[string]interface{}),
EventListeners: make(map[string][]func(Object)),
Classes: make(FakeClassList),
}
}
// ID returns e.Get("id").String() (so, set the embedded FakeObject's id property).
func (e *FakeElement) ID() string {
return e.Get("id").String()
}
// GetAttribute gets an attribute value.
func (e *FakeElement) GetAttribute(attr string) Object {
return MakeFakeObject(e.Attributes[attr])
}
// SetAttribute sets an attribute.
func (e *FakeElement) SetAttribute(attr string, value interface{}) Element {
e.Attributes[attr] = value
return e
}
// RemoveAttribute removes an attribute.
func (e *FakeElement) RemoveAttribute(attr string) Element {
delete(e.Attributes, attr)
return e
}
// AddChildren adds child elements (*FakeElement only).
func (e *FakeElement) AddChildren(children ...Element) Element {
for _, c := range children {
if d, ok := c.(*FakeElement); ok {
e.Children = append(e.Children, d)
d.parent = e
}
}
return e
}
// RemoveChildren removes child elements. It does it with the straightforward, naïve
// O(len(e.Children) * len(children)) method.
func (e *FakeElement) RemoveChildren(children ...Element) Element {
if len(children) == 0 {
return e
}
rem := make([]*FakeElement, 0, len(e.Children))
outer:
for _, c := range e.Children {
for _, x := range children {
if c == x {
c.parent = nil
continue outer
}
}
rem = append(rem, c)
}
e.Children = rem
return e
}
// AddEventListener adds an event listener.
func (e *FakeElement) AddEventListener(event string, handler func(Object)) Element {
e.EventListeners[event] = append(e.EventListeners[event], handler)
return e
}
// Show removes the display attribute.
func (e *FakeElement) Show() Element {
return e.RemoveAttribute("display")
}
// Hide sets the display attribute to "none".
func (e *FakeElement) Hide() Element {
return e.SetAttribute("display", "none")
}
// Display sets the display attribute to whatever.
func (e *FakeElement) Display(style string) Element {
return e.SetAttribute("display", style)
}
// Parent returns the parent element.
func (e *FakeElement) Parent() Element {
return e.parent
}
// ClassList returns the list of classes.
func (e *FakeElement) ClassList() ClassList {
return e.Classes
}
// FakeDocument implements a fake Document.
type FakeDocument struct {
FakeElement
}
// MakeFakeDocument makes a fake document.
func MakeFakeDocument() *FakeDocument {
d := &FakeDocument{
FakeElement: *MakeFakeElement("document", XHTMLNamespaceURI),
}
d.AddChildren(&FakeElement{
Class: "body",
})
return d
}
// ElementByID searches the fake document for a matching element.
func (d *FakeDocument) ElementByID(id string) Element {
q := []*FakeElement{&d.FakeElement}
for len(q) > 0 {
e := q[0]
if e.ID() == id {
return e
}
q = append(q[1:], e.Children...)
}
return nil
}
// MakeTextNode makes something that looks like a text node.
func (d *FakeDocument) MakeTextNode(text string) Element {
e := MakeFakeElement("text", "")
e.Set("wholeText", text)
return e
}
// MakeSVGElement makes an SVG element.
func (d *FakeDocument) MakeSVGElement(class string) Element {
e := MakeFakeElement(class, SVGNamespaceURI)
switch class {
case "text":
e.Methods["getComputedTextLength"] = func(...interface{}) interface{} {
return rand.Float64() * 200
}
e.Methods["getBBox"] = func(...interface{}) interface{} {
o := MakeFakeObject(nil)
o.Set("x", 150.0)
o.Set("y", 160.0)
o.Set("height", 40.0)
o.Set("width", 130.0)
return o
}
}
return e
}