248 lines
8.9 KiB
Go
248 lines
8.9 KiB
Go
package css // import "github.com/tdewolff/parse/css"
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"testing"
|
|
|
|
"github.com/tdewolff/parse"
|
|
"github.com/tdewolff/test"
|
|
)
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
func TestParse(t *testing.T) {
|
|
var parseTests = []struct {
|
|
inline bool
|
|
css string
|
|
expected string
|
|
}{
|
|
{true, " x : y ; ", "x:y;"},
|
|
{true, "color: red;", "color:red;"},
|
|
{true, "color : red;", "color:red;"},
|
|
{true, "color: red; border: 0;", "color:red;border:0;"},
|
|
{true, "color: red !important;", "color:red!important;"},
|
|
{true, "color: red ! important;", "color:red!important;"},
|
|
{true, "white-space: -moz-pre-wrap;", "white-space:-moz-pre-wrap;"},
|
|
{true, "display: -moz-inline-stack;", "display:-moz-inline-stack;"},
|
|
{true, "x: 10px / 1em;", "x:10px/1em;"},
|
|
{true, "x: 1em/1.5em \"Times New Roman\", Times, serif;", "x:1em/1.5em \"Times New Roman\",Times,serif;"},
|
|
{true, "x: hsla(100,50%, 75%, 0.5);", "x:hsla(100,50%,75%,0.5);"},
|
|
{true, "x: hsl(100,50%, 75%);", "x:hsl(100,50%,75%);"},
|
|
{true, "x: rgba(255, 238 , 221, 0.3);", "x:rgba(255,238,221,0.3);"},
|
|
{true, "x: 50vmax;", "x:50vmax;"},
|
|
{true, "color: linear-gradient(to right, black, white);", "color:linear-gradient(to right,black,white);"},
|
|
{true, "color: calc(100%/2 - 1em);", "color:calc(100%/2 - 1em);"},
|
|
{true, "color: calc(100%/2--1em);", "color:calc(100%/2--1em);"},
|
|
{false, "<!-- @charset; -->", "<!--@charset;-->"},
|
|
{false, "@media print, screen { }", "@media print,screen{}"},
|
|
{false, "@media { @viewport ; }", "@media{@viewport;}"},
|
|
{false, "@keyframes 'diagonal-slide' { from { left: 0; top: 0; } to { left: 100px; top: 100px; } }", "@keyframes 'diagonal-slide'{from{left:0;top:0;}to{left:100px;top:100px;}}"},
|
|
{false, "@keyframes movingbox{0%{left:90%;}50%{left:10%;}100%{left:90%;}}", "@keyframes movingbox{0%{left:90%;}50%{left:10%;}100%{left:90%;}}"},
|
|
{false, ".foo { color: #fff;}", ".foo{color:#fff;}"},
|
|
{false, ".foo { ; _color: #fff;}", ".foo{_color:#fff;}"},
|
|
{false, "a { color: red; border: 0; }", "a{color:red;border:0;}"},
|
|
{false, "a { color: red; border: 0; } b { padding: 0; }", "a{color:red;border:0;}b{padding:0;}"},
|
|
{false, "/* comment */", "/* comment */"},
|
|
|
|
// extraordinary
|
|
{true, "color: red;;", "color:red;"},
|
|
{true, "color:#c0c0c0", "color:#c0c0c0;"},
|
|
{true, "background:URL(x.png);", "background:URL(x.png);"},
|
|
{true, "filter: progid : DXImageTransform.Microsoft.BasicImage(rotation=1);", "filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);"},
|
|
{true, "/*a*/\n/*c*/\nkey: value;", "key:value;"},
|
|
{true, "@-moz-charset;", "@-moz-charset;"},
|
|
{true, "--custom-variable: (0;) ;", "--custom-variable: (0;) ;"},
|
|
{false, "@import;@import;", "@import;@import;"},
|
|
{false, ".a .b#c, .d<.e { x:y; }", ".a .b#c,.d<.e{x:y;}"},
|
|
{false, ".a[b~=c]d { x:y; }", ".a[b~=c]d{x:y;}"},
|
|
// {false, "{x:y;}", "{x:y;}"},
|
|
{false, "a{}", "a{}"},
|
|
{false, "a,.b/*comment*/ {x:y;}", "a,.b{x:y;}"},
|
|
{false, "a,.b/*comment*/.c {x:y;}", "a,.b.c{x:y;}"},
|
|
{false, "a{x:; z:q;}", "a{x:;z:q;}"},
|
|
{false, "@font-face { x:y; }", "@font-face{x:y;}"},
|
|
{false, "a:not([controls]){x:y;}", "a:not([controls]){x:y;}"},
|
|
{false, "@document regexp('https:.*') { p { color: red; } }", "@document regexp('https:.*'){p{color:red;}}"},
|
|
{false, "@media all and ( max-width:400px ) { }", "@media all and (max-width:400px){}"},
|
|
{false, "@media (max-width:400px) { }", "@media(max-width:400px){}"},
|
|
{false, "@media (max-width:400px)", "@media(max-width:400px);"},
|
|
{false, "@font-face { ; font:x; }", "@font-face{font:x;}"},
|
|
{false, "@-moz-font-face { ; font:x; }", "@-moz-font-face{font:x;}"},
|
|
{false, "@unknown abc { {} lala }", "@unknown abc{{}lala}"},
|
|
{false, "a[x={}]{x:y;}", "a[x={}]{x:y;}"},
|
|
{false, "a[x=,]{x:y;}", "a[x=,]{x:y;}"},
|
|
{false, "a[x=+]{x:y;}", "a[x=+]{x:y;}"},
|
|
{false, ".cla .ss > #id { x:y; }", ".cla .ss>#id{x:y;}"},
|
|
{false, ".cla /*a*/ /*b*/ .ss{}", ".cla .ss{}"},
|
|
{false, "a{x:f(a(),b);}", "a{x:f(a(),b);}"},
|
|
{false, "a{x:y!z;}", "a{x:y!z;}"},
|
|
{false, "[class*=\"column\"]+[class*=\"column\"]:last-child{a:b;}", "[class*=\"column\"]+[class*=\"column\"]:last-child{a:b;}"},
|
|
{false, "@media { @viewport }", "@media{@viewport;}"},
|
|
{false, "table { @unknown }", "table{@unknown;}"},
|
|
|
|
// early endings
|
|
{false, "selector{", "selector{"},
|
|
{false, "@media{selector{", "@media{selector{"},
|
|
|
|
// bad grammar
|
|
{true, "~color:red", "~color:red;"},
|
|
{false, ".foo { *color: #fff;}", ".foo{*color:#fff;}"},
|
|
{true, "*color: red; font-size: 12pt;", "*color:red;font-size:12pt;"},
|
|
{true, "_color: red; font-size: 12pt;", "_color:red;font-size:12pt;"},
|
|
|
|
// issues
|
|
{false, "@media print {.class{width:5px;}}", "@media print{.class{width:5px;}}"}, // #6
|
|
{false, ".class{width:calc((50% + 2em)/2 + 14px);}", ".class{width:calc((50% + 2em)/2 + 14px);}"}, // #7
|
|
{false, ".class [c=y]{}", ".class [c=y]{}"}, // tdewolff/minify#16
|
|
{false, "table{font-family:Verdana}", "table{font-family:Verdana;}"}, // tdewolff/minify#22
|
|
|
|
// go-fuzz
|
|
{false, "@-webkit-", "@-webkit-;"},
|
|
}
|
|
for _, tt := range parseTests {
|
|
t.Run(tt.css, func(t *testing.T) {
|
|
output := ""
|
|
p := NewParser(bytes.NewBufferString(tt.css), tt.inline)
|
|
for {
|
|
grammar, _, data := p.Next()
|
|
data = parse.Copy(data)
|
|
if grammar == ErrorGrammar {
|
|
if err := p.Err(); err != io.EOF {
|
|
for _, val := range p.Values() {
|
|
data = append(data, val.Data...)
|
|
}
|
|
if perr, ok := err.(*parse.Error); ok && perr.Message == "unexpected token in declaration" {
|
|
data = append(data, ";"...)
|
|
}
|
|
} else {
|
|
test.T(t, err, io.EOF)
|
|
break
|
|
}
|
|
} else if grammar == AtRuleGrammar || grammar == BeginAtRuleGrammar || grammar == QualifiedRuleGrammar || grammar == BeginRulesetGrammar || grammar == DeclarationGrammar || grammar == CustomPropertyGrammar {
|
|
if grammar == DeclarationGrammar || grammar == CustomPropertyGrammar {
|
|
data = append(data, ":"...)
|
|
}
|
|
for _, val := range p.Values() {
|
|
data = append(data, val.Data...)
|
|
}
|
|
if grammar == BeginAtRuleGrammar || grammar == BeginRulesetGrammar {
|
|
data = append(data, "{"...)
|
|
} else if grammar == AtRuleGrammar || grammar == DeclarationGrammar || grammar == CustomPropertyGrammar {
|
|
data = append(data, ";"...)
|
|
} else if grammar == QualifiedRuleGrammar {
|
|
data = append(data, ","...)
|
|
}
|
|
}
|
|
output += string(data)
|
|
}
|
|
test.String(t, output, tt.expected)
|
|
})
|
|
}
|
|
|
|
test.T(t, ErrorGrammar.String(), "Error")
|
|
test.T(t, AtRuleGrammar.String(), "AtRule")
|
|
test.T(t, BeginAtRuleGrammar.String(), "BeginAtRule")
|
|
test.T(t, EndAtRuleGrammar.String(), "EndAtRule")
|
|
test.T(t, BeginRulesetGrammar.String(), "BeginRuleset")
|
|
test.T(t, EndRulesetGrammar.String(), "EndRuleset")
|
|
test.T(t, DeclarationGrammar.String(), "Declaration")
|
|
test.T(t, TokenGrammar.String(), "Token")
|
|
test.T(t, CommentGrammar.String(), "Comment")
|
|
test.T(t, CustomPropertyGrammar.String(), "CustomProperty")
|
|
test.T(t, GrammarType(100).String(), "Invalid(100)")
|
|
}
|
|
|
|
func TestParseError(t *testing.T) {
|
|
var parseErrorTests = []struct {
|
|
inline bool
|
|
css string
|
|
col int
|
|
}{
|
|
{false, "selector", 9},
|
|
{true, "color 0", 8},
|
|
{true, "--color 0", 10},
|
|
{true, "--custom-variable:0", 0},
|
|
}
|
|
for _, tt := range parseErrorTests {
|
|
t.Run(tt.css, func(t *testing.T) {
|
|
p := NewParser(bytes.NewBufferString(tt.css), tt.inline)
|
|
for {
|
|
grammar, _, _ := p.Next()
|
|
if grammar == ErrorGrammar {
|
|
if tt.col == 0 {
|
|
test.T(t, p.Err(), io.EOF)
|
|
} else if perr, ok := p.Err().(*parse.Error); ok {
|
|
test.T(t, perr.Col, tt.col)
|
|
} else {
|
|
test.Fail(t, "bad error:", p.Err())
|
|
}
|
|
break
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReader(t *testing.T) {
|
|
input := "x:a;"
|
|
p := NewParser(test.NewPlainReader(bytes.NewBufferString(input)), true)
|
|
for {
|
|
grammar, _, _ := p.Next()
|
|
if grammar == ErrorGrammar {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
type Obj struct{}
|
|
|
|
func (*Obj) F() {}
|
|
|
|
var f1 func(*Obj)
|
|
|
|
func BenchmarkFuncPtr(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
f1 = (*Obj).F
|
|
}
|
|
}
|
|
|
|
var f2 func()
|
|
|
|
func BenchmarkMemFuncPtr(b *testing.B) {
|
|
obj := &Obj{}
|
|
for i := 0; i < b.N; i++ {
|
|
f2 = obj.F
|
|
}
|
|
}
|
|
|
|
func ExampleNewParser() {
|
|
p := NewParser(bytes.NewBufferString("color: red;"), true) // false because this is the content of an inline style attribute
|
|
out := ""
|
|
for {
|
|
gt, _, data := p.Next()
|
|
if gt == ErrorGrammar {
|
|
break
|
|
} else if gt == AtRuleGrammar || gt == BeginAtRuleGrammar || gt == BeginRulesetGrammar || gt == DeclarationGrammar {
|
|
out += string(data)
|
|
if gt == DeclarationGrammar {
|
|
out += ":"
|
|
}
|
|
for _, val := range p.Values() {
|
|
out += string(val.Data)
|
|
}
|
|
if gt == BeginAtRuleGrammar || gt == BeginRulesetGrammar {
|
|
out += "{"
|
|
} else if gt == AtRuleGrammar || gt == DeclarationGrammar {
|
|
out += ";"
|
|
}
|
|
} else {
|
|
out += string(data)
|
|
}
|
|
}
|
|
fmt.Println(out)
|
|
// Output: color:red;
|
|
}
|