hatmill/hatmill_test.go

256 lines
6.4 KiB
Go
Raw Normal View History

2019-03-21 21:52:32 +00:00
package hatmill_test
import (
"bytes"
2019-03-22 02:03:24 +00:00
"fmt"
2019-04-03 03:42:16 +00:00
"html"
2019-03-22 02:03:24 +00:00
"io"
2019-04-03 03:42:16 +00:00
"os"
"reflect"
"strings"
2019-03-22 02:03:24 +00:00
"testing"
2019-04-29 04:03:28 +00:00
"unicode"
2019-03-22 02:03:24 +00:00
2019-03-21 21:52:32 +00:00
"github.com/leanovate/gopter"
"github.com/leanovate/gopter/gen"
"github.com/leanovate/gopter/prop"
2019-03-22 04:30:27 +00:00
"gitlab.codemonkeysoftware.net/b/hatmill"
2019-04-03 03:42:16 +00:00
ha "gitlab.codemonkeysoftware.net/b/hatmill/attribute"
he "gitlab.codemonkeysoftware.net/b/hatmill/element"
2019-03-21 21:52:32 +00:00
)
type NopWriter struct{}
func (NopWriter) Write(p []byte) (n int, err error) {
return len(p), nil
}
2019-03-22 02:03:24 +00:00
func writeToString(wt io.WriterTo) string {
var buf bytes.Buffer
_, err := wt.WriteTo(&buf)
if err != nil {
panic(err)
}
return buf.String()
}
func checkWrite(wt io.WriterTo, expected string) bool {
var buf bytes.Buffer
n, err := wt.WriteTo(&buf)
return err == nil && n == int64(buf.Len()) && buf.String() == expected
}
2019-04-29 04:03:28 +00:00
var ASCII = &unicode.RangeTable{
R16: []unicode.Range16{
{Lo: ' ', Hi: unicode.MaxASCII, Stride: 1},
},
LatinOffset: 1,
}
var dangerousASCII = gen.UnicodeString(ASCII).SuchThat(func(v interface{}) bool {
s := v.(string)
const specialChars = `&<>'"`
for _, c := range specialChars {
if strings.ContainsRune(s, rune(c)) {
return true
}
}
return false
})
func printableASCII() string {
const minPrintable rune = 32
const maxPrintable rune = 126
var buf bytes.Buffer
for r := minPrintable; r <= maxPrintable; r++ {
buf.WriteRune(r)
}
return buf.String()
}
2019-03-21 21:52:32 +00:00
func TestText(t *testing.T) {
properties := gopter.NewProperties(nil)
2019-04-29 04:03:28 +00:00
properties.Property("WriteTo escapes special chars and outputs all others unchanged", prop.ForAll(
2019-03-21 21:52:32 +00:00
func(s string) bool {
2019-04-29 04:03:28 +00:00
expected := html.EscapeString(s)
return checkWrite(hatmill.Text(s), expected)
2019-03-21 21:52:32 +00:00
},
2019-04-29 04:03:28 +00:00
dangerousASCII,
2019-03-21 21:52:32 +00:00
))
2019-03-22 02:03:24 +00:00
properties.TestingRun(t)
}
2019-04-29 04:03:28 +00:00
func stringNotEmpty(v interface{}) bool {
2019-03-22 02:03:24 +00:00
return v.(string) != ""
2019-04-29 04:03:28 +00:00
}
2019-03-22 02:03:24 +00:00
2019-04-29 04:03:28 +00:00
var nonEmptyAlphaString = gen.AlphaString().SuchThat(stringNotEmpty)
func attribGen(value gopter.Gen) gopter.Gen {
return gen.Struct(reflect.TypeOf(hatmill.Attrib{}), map[string]gopter.Gen{
"Key": nonEmptyAlphaString,
"Value": value,
})
}
2019-03-22 02:03:24 +00:00
func TestAttrib(t *testing.T) {
properties := gopter.NewProperties(nil)
properties.Property("WriteTo only writes key when value is empty", prop.ForAll(
func(key string) bool {
attrib := hatmill.Attrib{
Key: key,
}
return checkWrite(attrib, key)
2019-03-21 21:52:32 +00:00
},
2019-03-22 02:03:24 +00:00
nonEmptyAlphaString,
2019-03-21 21:52:32 +00:00
))
2019-03-22 02:03:24 +00:00
properties.Property("writes key=value when value is not empty", prop.ForAll(
func(attrib hatmill.Attrib) bool {
expected := fmt.Sprintf("%s='%s'", attrib.Key, attrib.Value)
return checkWrite(attrib, expected)
2019-03-21 21:52:32 +00:00
},
2019-04-29 04:03:28 +00:00
attribGen(nonEmptyAlphaString),
))
properties.Property("WriteTo escapes attribute value", prop.ForAll(
func(attrib hatmill.Attrib) bool {
expected := fmt.Sprintf("%s='%s'", attrib.Key, html.EscapeString(attrib.Value))
return checkWrite(attrib, expected)
},
attribGen(dangerousASCII.SuchThat(stringNotEmpty)),
2019-03-21 21:52:32 +00:00
))
properties.TestingRun(t)
}
2019-03-22 02:03:24 +00:00
func TestEmptyElement(t *testing.T) {
properties := gopter.NewProperties(nil)
properties.Property("WriteTo writes element correctly with no attributes", prop.ForAll(
func(tagName string) bool {
2019-03-29 02:18:25 +00:00
elem := &hatmill.VoidElement{
2019-03-22 02:03:24 +00:00
TagName: tagName,
}
expected := "<" + tagName + ">"
return checkWrite(elem, expected)
},
gen.AnyString(),
))
properties.Property("WriteTo writes element correctly with attributes", prop.ForAll(
func(tagName string, attribs []hatmill.Attrib) bool {
2019-03-29 02:18:25 +00:00
elem := hatmill.VoidElement{
TagName: tagName,
Attribs: attribs,
}
var attribStrings []string
for _, attrib := range attribs {
attribStrings = append(attribStrings, writeToString(attrib))
}
if len(attribStrings) > 0 {
attribStrings[0] = " " + attribStrings[0]
}
expected := "<" + tagName + strings.Join(attribStrings, " ") + ">"
return checkWrite(elem, expected)
},
gen.AnyString(),
2019-04-29 04:03:28 +00:00
gen.SliceOf(attribGen(gen.AlphaString())),
))
properties.TestingRun(t)
2019-03-22 02:03:24 +00:00
}
2019-03-22 03:31:24 +00:00
var htmlTextGen = gen.AnyString().Map(func(s string) hatmill.Term {
return hatmill.Text(s)
2019-03-22 02:03:24 +00:00
})
func TestParentElement(t *testing.T) {
properties := gopter.NewProperties(nil)
properties.Property("WriteTo writes element correctly without children", prop.ForAll(
func(tagName string, attribs []hatmill.Attrib) bool {
elem := &hatmill.ParentElement{
2019-03-29 02:18:25 +00:00
VoidElement: hatmill.VoidElement{
TagName: tagName,
Attribs: attribs,
},
}
2019-03-29 02:18:25 +00:00
openTag := writeToString(elem.VoidElement)
expected := openTag + "</" + tagName + ">"
return checkWrite(elem, expected)
},
gen.AnyString(),
2019-04-29 04:03:28 +00:00
gen.SliceOf(attribGen(gen.AlphaString())),
))
properties.Property("WriteTo writes element correctly with children", prop.ForAll(
func(tagName string, attribs []hatmill.Attrib, children []hatmill.Term) bool {
elem := &hatmill.ParentElement{
2019-03-29 02:18:25 +00:00
VoidElement: hatmill.VoidElement{
TagName: tagName,
Attribs: attribs,
},
Children: children,
}
2019-03-29 02:18:25 +00:00
openTag := writeToString(elem.VoidElement)
var childStrings []string
for _, child := range children {
childStrings = append(childStrings, writeToString(child))
}
expected := openTag + strings.Join(childStrings, "") + "</" + tagName + ">"
return checkWrite(elem, expected)
},
gen.AnyString(),
2019-04-29 04:03:28 +00:00
gen.SliceOf(attribGen(gen.AlphaString())),
gen.SliceOf(htmlTextGen),
))
properties.TestingRun(t)
2019-03-22 02:03:24 +00:00
}
2019-04-03 03:42:16 +00:00
2019-04-28 23:05:24 +00:00
func TestTerms(t *testing.T) {
properties := gopter.NewProperties(nil)
properties.Property("WriteTo writes the concatenation of calling WriteTo on the slice contents", prop.ForAll(
func(terms []hatmill.Term) bool {
var expected string
for _, term := range terms {
expected += string(term.(hatmill.Text))
}
return checkWrite(hatmill.Terms(terms), expected)
},
gen.SliceOf(htmlTextGen),
))
}
2019-05-28 04:34:01 +00:00
func ExampleRawText() {
he.Style()(
hatmill.RawText(`div > p::before {content: "Words & stuff: ";}`),
).WriteTo(os.Stdout)
// Output: <style>div > p::before {content: "Words & stuff: ";}</style>
}
2019-04-03 03:42:16 +00:00
func Example() {
2019-04-09 04:10:02 +00:00
userInput := "<script>launchMissiles();</script>"
2019-04-03 03:42:16 +00:00
2019-04-09 04:10:02 +00:00
document := he.Html()(
2019-04-03 03:42:16 +00:00
he.Body()(
2019-04-27 02:49:10 +00:00
he.Img(ha.Src("./photo.jpg"), ha.Contenteditable(true)),
2019-04-29 04:03:28 +00:00
hatmill.Text(userInput),
2019-04-23 04:12:12 +00:00
he.Div(ha.Disabled(), ha.CustomData("coolness", "awesome"))(),
he.Textarea(ha.Rows(25))(),
he.Meter(ha.Min(-1.3), ha.Max(5.5E12))(),
2019-04-03 03:42:16 +00:00
),
)
hatmill.WriteDocument(os.Stdout, document)
2019-04-27 02:49:10 +00:00
// Output: <!DOCTYPE html><html><body><img src='./photo.jpg' contenteditable='true'>&lt;script&gt;launchMissiles();&lt;/script&gt;<div disabled data-coolness='awesome'></div><textarea rows='25'></textarea><meter min='-1.3' max='5.5E+12'></meter></body></html>
2019-04-03 03:42:16 +00:00
}