package hatmill_test import ( "bytes" "fmt" "html" "io" "os" "reflect" "strings" "testing" "unicode" "github.com/leanovate/gopter" "github.com/leanovate/gopter/gen" "github.com/leanovate/gopter/prop" "gitlab.codemonkeysoftware.net/b/hatmill" ha "gitlab.codemonkeysoftware.net/b/hatmill/attribute" he "gitlab.codemonkeysoftware.net/b/hatmill/element" ) type NopWriter struct{} func (NopWriter) Write(p []byte) (n int, err error) { return len(p), nil } 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 } 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() } func TestText(t *testing.T) { properties := gopter.NewProperties(nil) properties.Property("WriteTo escapes special chars and outputs all others unchanged", prop.ForAll( func(s string) bool { expected := html.EscapeString(s) return checkWrite(hatmill.Text(s), expected) }, dangerousASCII, )) properties.TestingRun(t) } func stringNotEmpty(v interface{}) bool { return v.(string) != "" } 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, }) } 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) }, nonEmptyAlphaString, )) 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) }, 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)), )) properties.TestingRun(t) } func TestEmptyElement(t *testing.T) { properties := gopter.NewProperties(nil) properties.Property("WriteTo writes element correctly with no attributes", prop.ForAll( func(tagName string) bool { elem := &hatmill.VoidElement{ 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 { 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(), gen.SliceOf(attribGen(gen.AlphaString())), )) properties.TestingRun(t) } var htmlTextGen = gen.AnyString().Map(func(s string) hatmill.Term { return hatmill.Text(s) }) 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{ VoidElement: hatmill.VoidElement{ TagName: tagName, Attribs: attribs, }, } openTag := writeToString(elem.VoidElement) expected := openTag + "" + tagName + ">" return checkWrite(elem, expected) }, gen.AnyString(), 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{ VoidElement: hatmill.VoidElement{ TagName: tagName, Attribs: attribs, }, Children: children, } 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(), gen.SliceOf(attribGen(gen.AlphaString())), gen.SliceOf(htmlTextGen), )) properties.TestingRun(t) } 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), )) } func Example() { userInput := "" document := he.Html()( he.Body()( he.Img(ha.Src("./photo.jpg"), ha.Contenteditable(true)), hatmill.Text(userInput), he.Div(ha.Disabled(), ha.CustomData("coolness", "awesome"))(), he.Textarea(ha.Rows(25))(), he.Meter(ha.Min(-1.3), ha.Max(5.5E12))(), ), ) hatmill.WriteDocument(os.Stdout, document) // Output:
<script>launchMissiles();</script>