css_test.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. package css // import "github.com/tdewolff/minify/css"
  2. import (
  3. "bytes"
  4. "fmt"
  5. "os"
  6. "testing"
  7. "github.com/tdewolff/minify"
  8. "github.com/tdewolff/test"
  9. )
  10. func TestCSS(t *testing.T) {
  11. cssTests := []struct {
  12. css string
  13. expected string
  14. }{
  15. {"/*comment*/", ""},
  16. {"/*! bang comment */", "/*!bang comment*/"},
  17. {"i{}/*! bang comment */", "i{}/*!bang comment*/"},
  18. {"i { key: value; key2: value; }", "i{key:value;key2:value}"},
  19. {".cla .ss > #id { x:y; }", ".cla .ss>#id{x:y}"},
  20. {".cla[id ^= L] { x:y; }", ".cla[id^=L]{x:y}"},
  21. {"area:focus { outline : 0;}", "area:focus{outline:0}"},
  22. {"@import 'file';", "@import 'file'"},
  23. {"@import url('file');", "@import 'file'"},
  24. {"@import url(//url);", `@import "//url"`},
  25. {"@font-face { x:y; }", "@font-face{x:y}"},
  26. {"input[type=\"radio\"]{x:y}", "input[type=radio]{x:y}"},
  27. {"DIV{margin:1em}", "div{margin:1em}"},
  28. {".CLASS{margin:1em}", ".CLASS{margin:1em}"},
  29. {"@MEDIA all{}", "@media all{}"},
  30. {"@media only screen and (max-width : 800px){}", "@media only screen and (max-width:800px){}"},
  31. {"@media (-webkit-min-device-pixel-ratio:1.5),(min-resolution:1.5dppx){}", "@media(-webkit-min-device-pixel-ratio:1.5),(min-resolution:1.5dppx){}"},
  32. {"[class^=icon-] i[class^=icon-],i[class*=\" icon-\"]{x:y}", "[class^=icon-] i[class^=icon-],i[class*=\" icon-\"]{x:y}"},
  33. {"html{line-height:1;}html{line-height:1;}", "html{line-height:1}html{line-height:1}"},
  34. {"a { b: 1", "a{b:1}"},
  35. {":root { --custom-variable:0px; }", ":root{--custom-variable:0px}"},
  36. // case sensitivity
  37. {"@counter-style Ident{}", "@counter-style Ident{}"},
  38. // coverage
  39. {"a, b + c { x:y; }", "a,b+c{x:y}"},
  40. // bad declaration
  41. {".clearfix { *zoom: 1px; }", ".clearfix{*zoom:1px}"},
  42. {".clearfix { *zoom: 1px }", ".clearfix{*zoom:1px}"},
  43. {".clearfix { color:green; *zoom: 1px; color:red; }", ".clearfix{color:green;*zoom:1px;color:red}"},
  44. // go-fuzz
  45. {"input[type=\"\x00\"] { a: b\n}.a{}", "input[type=\"\x00\"]{a:b}.a{}"},
  46. {"a{a:)'''", "a{a:)'''}"},
  47. {"{T:l(", "{t:l(}"},
  48. }
  49. m := minify.New()
  50. for _, tt := range cssTests {
  51. t.Run(tt.css, func(t *testing.T) {
  52. r := bytes.NewBufferString(tt.css)
  53. w := &bytes.Buffer{}
  54. err := Minify(m, w, r, nil)
  55. test.Minify(t, tt.css, err, w.String(), tt.expected)
  56. })
  57. }
  58. }
  59. func TestCSSInline(t *testing.T) {
  60. cssTests := []struct {
  61. css string
  62. expected string
  63. }{
  64. {"/*comment*/", ""},
  65. {"/*! bang comment */", ""},
  66. {";", ""},
  67. {"empty:", "empty:"},
  68. {"key: value;", "key:value"},
  69. {"margin: 0 1; padding: 0 1;", "margin:0 1;padding:0 1"},
  70. {"color: #FF0000;", "color:red"},
  71. {"color: #000000;", "color:#000"},
  72. {"color: black;", "color:#000"},
  73. {"color: rgb(255,255,255);", "color:#fff"},
  74. {"color: rgb(100%,100%,100%);", "color:#fff"},
  75. {"color: rgba(255,0,0,1);", "color:red"},
  76. {"color: rgba(255,0,0,2);", "color:red"},
  77. {"color: rgba(255,0,0,0.5);", "color:rgba(255,0,0,.5)"},
  78. {"color: rgba(255,0,0,-1);", "color:transparent"},
  79. {"color: rgba(0%,15%,25%,0.2);", "color:rgba(0%,15%,25%,.2)"},
  80. {"color: rgba(0,0,0,0.5);", "color:rgba(0,0,0,.5)"},
  81. {"color: hsla(5,0%,10%,0.75);", "color:hsla(5,0%,10%,.75)"},
  82. {"color: hsl(0,100%,50%);", "color:red"},
  83. {"color: hsla(1,2%,3%,1);", "color:#080807"},
  84. {"color: hsla(1,2%,3%,0);", "color:transparent"},
  85. {"color: hsl(48,100%,50%);", "color:#fc0"},
  86. {"background: hsla(0,0%,100%,.7);", "background:hsla(0,0%,100%,.7)"},
  87. {"font-weight: bold; font-weight: normal;", "font-weight:700;font-weight:400"},
  88. {"font: bold \"Times new Roman\",\"Sans-Serif\";", "font:700 times new roman,\"sans-serif\""},
  89. {"font: normal normal normal normal 20px normal", "font:20px normal"},
  90. {"outline: none;", "outline:0"},
  91. {"outline: solid black 0;", "outline:solid #000 0"},
  92. {"outline: none black 5px;", "outline:0 #000 5px"},
  93. {"outline: none !important;", "outline:0!important"},
  94. {"border-left: none;", "border-left:0"},
  95. {"border-left: none 0;", "border-left:0"},
  96. {"border-left: 0 dashed red;", "border-left:0 dashed red"},
  97. {"margin: 1 1 1 1;", "margin:1"},
  98. {"margin: 1 2 1 2;", "margin:1 2"},
  99. {"margin: 1 2 3 2;", "margin:1 2 3"},
  100. {"margin: 1 2 3 4;", "margin:1 2 3 4"},
  101. {"margin: 1 1 1 a;", "margin:1 1 1 a"},
  102. {"margin: 1 1 1 1 !important;", "margin:1!important"},
  103. {"padding:.2em .4em .2em", "padding:.2em .4em"},
  104. {"margin: 0em;", "margin:0"},
  105. {"font-family:'Arial', 'Times New Roman';", "font-family:arial,times new roman"},
  106. {"background:url('http://domain.com/image.png');", "background:url(http://domain.com/image.png)"},
  107. {"background:url( 'http://domain.com/image.png' );", "background:url(http://domain.com/image.png)"},
  108. {"filter: progid : DXImageTransform.Microsoft.BasicImage(rotation=1);", "filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"},
  109. {"filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);", "filter:alpha(opacity=0)"},
  110. {"content: \"a\\\nb\";", "content:\"ab\""},
  111. {"content: \"a\\\r\nb\\\r\nc\";", "content:\"abc\""},
  112. {"content: \"\";", "content:\"\""},
  113. {"x: white , white", "x:#fff,#fff"},
  114. {"font:27px/13px arial,sans-serif", "font:27px/13px arial,sans-serif"},
  115. {"text-decoration: none !important", "text-decoration:none!important"},
  116. {"color:#fff", "color:#fff"},
  117. {"border:2px rgb(255,255,255);", "border:2px #fff"},
  118. {"margin:-1px", "margin:-1px"},
  119. {"margin:+1px", "margin:1px"},
  120. {"margin:0.5em", "margin:.5em"},
  121. {"margin:-0.5em", "margin:-.5em"},
  122. {"margin:05em", "margin:5em"},
  123. {"margin:.50em", "margin:.5em"},
  124. {"margin:5.0em", "margin:5em"},
  125. {"margin:5000em", "margin:5e3em"},
  126. {"color:#c0c0c0", "color:silver"},
  127. {"-ms-filter: \"progid:DXImageTransform.Microsoft.Alpha(Opacity=80)\";", "-ms-filter:\"alpha(opacity=80)\""},
  128. {"filter: progid:DXImageTransform.Microsoft.Alpha(Opacity = 80);", "filter:alpha(opacity=80)"},
  129. {"MARGIN:1EM", "margin:1em"},
  130. //{"color:CYAN", "color:cyan"}, // TODO
  131. {"width:attr(Name em)", "width:attr(Name em)"},
  132. {"content:CounterName", "content:CounterName"},
  133. {"background:URL(x.PNG);", "background:url(x.PNG)"},
  134. {"background:url(/*nocomment*/)", "background:url(/*nocomment*/)"},
  135. {"background:url(data:,text)", "background:url(data:,text)"},
  136. {"background:url('data:text/xml; version = 2.0,content')", "background:url(data:text/xml;version=2.0,content)"},
  137. {"background:url('data:\\'\",text')", "background:url('data:\\'\",text')"},
  138. {"margin:0 0 18px 0;", "margin:0 0 18px"},
  139. {"background:none", "background:0 0"},
  140. {"background:none 1 1", "background:none 1 1"},
  141. {"background:transparent", "background:0 0"},
  142. {"background:transparent no-repeat", "background:transparent no-repeat"},
  143. {"z-index:1000", "z-index:1000"},
  144. {"box-shadow:0 0 0 0", "box-shadow:0 0"},
  145. {"flex:0px", "flex:0px"},
  146. {"any:0deg 0s 0ms 0dpi 0dpcm 0dppx 0hz 0khz", "any:0 0s 0ms 0dpi 0dpcm 0dppx 0hz 0khz"},
  147. {"width:calc(0%-0px)", "width:calc(0%-0px)"},
  148. {"border-left:0 none", "border-left:0"},
  149. {"--custom-variable:0px;", "--custom-variable:0px"},
  150. {"--foo: if(x > 5) this.width = 10", "--foo: if(x > 5) this.width = 10"},
  151. {"--foo: ;", "--foo: "},
  152. // case sensitivity
  153. {"animation:Ident", "animation:Ident"},
  154. {"animation-name:Ident", "animation-name:Ident"},
  155. // coverage
  156. {"margin: 1 1;", "margin:1"},
  157. {"margin: 1 2;", "margin:1 2"},
  158. {"margin: 1 1 1;", "margin:1"},
  159. {"margin: 1 2 1;", "margin:1 2"},
  160. {"margin: 1 2 3;", "margin:1 2 3"},
  161. // {"margin: 0%;", "margin:0"},
  162. {"color: rgb(255,64,64);", "color:#ff4040"},
  163. {"color: rgb(256,-34,2342435);", "color:#f0f"},
  164. {"color: rgb(120%,-45%,234234234%);", "color:#f0f"},
  165. {"color: rgb(0, 1, ident);", "color:rgb(0,1,ident)"},
  166. {"color: rgb(ident);", "color:rgb(ident)"},
  167. {"margin: rgb(ident);", "margin:rgb(ident)"},
  168. {"filter: progid:b().c.Alpha(rgba(x));", "filter:progid:b().c.Alpha(rgba(x))"},
  169. // go-fuzz
  170. {"FONT-FAMILY: ru\"", "font-family:ru\""},
  171. }
  172. m := minify.New()
  173. params := map[string]string{"inline": "1"}
  174. for _, tt := range cssTests {
  175. t.Run(tt.css, func(t *testing.T) {
  176. r := bytes.NewBufferString(tt.css)
  177. w := &bytes.Buffer{}
  178. err := Minify(m, w, r, params)
  179. test.Minify(t, tt.css, err, w.String(), tt.expected)
  180. })
  181. }
  182. }
  183. func TestCSSKeepCSS2(t *testing.T) {
  184. tests := []struct {
  185. css string
  186. expected string
  187. }{
  188. {`margin:5000em`, `margin:5000em`},
  189. }
  190. m := minify.New()
  191. params := map[string]string{"inline": "1"}
  192. cssMinifier := &Minifier{Decimals: -1, KeepCSS2: true}
  193. for _, tt := range tests {
  194. t.Run(tt.css, func(t *testing.T) {
  195. r := bytes.NewBufferString(tt.css)
  196. w := &bytes.Buffer{}
  197. err := cssMinifier.Minify(m, w, r, params)
  198. test.Minify(t, tt.css, err, w.String(), tt.expected)
  199. })
  200. }
  201. }
  202. func TestReaderErrors(t *testing.T) {
  203. r := test.NewErrorReader(0)
  204. w := &bytes.Buffer{}
  205. m := minify.New()
  206. err := Minify(m, w, r, nil)
  207. test.T(t, err, test.ErrPlain, "return error at first read")
  208. }
  209. func TestWriterErrors(t *testing.T) {
  210. errorTests := []struct {
  211. css string
  212. n []int
  213. }{
  214. {`@import 'file'`, []int{0, 2}},
  215. {`@media all{}`, []int{0, 2, 3, 4}},
  216. {`a[id^="L"]{margin:2in!important;color:red}`, []int{0, 4, 6, 7, 8, 9, 10, 11}},
  217. {`a{color:rgb(255,0,0)}`, []int{4}},
  218. {`a{color:rgb(255,255,255)}`, []int{4}},
  219. {`a{color:hsl(0,100%,50%)}`, []int{4}},
  220. {`a{color:hsl(360,100%,100%)}`, []int{4}},
  221. {`a{color:f(arg)}`, []int{4}},
  222. {`<!--`, []int{0}},
  223. {`/*!comment*/`, []int{0, 1, 2}},
  224. {`a{--var:val}`, []int{2, 3, 4}},
  225. {`a{*color:0}`, []int{2, 3}},
  226. {`a{color:0;baddecl 5}`, []int{5}},
  227. }
  228. m := minify.New()
  229. for _, tt := range errorTests {
  230. for _, n := range tt.n {
  231. t.Run(fmt.Sprint(tt.css, " ", tt.n), func(t *testing.T) {
  232. r := bytes.NewBufferString(tt.css)
  233. w := test.NewErrorWriter(n)
  234. err := Minify(m, w, r, nil)
  235. test.T(t, err, test.ErrPlain)
  236. })
  237. }
  238. }
  239. }
  240. ////////////////////////////////////////////////////////////////
  241. func ExampleMinify() {
  242. m := minify.New()
  243. m.AddFunc("text/css", Minify)
  244. if err := m.Minify("text/css", os.Stdout, os.Stdin); err != nil {
  245. panic(err)
  246. }
  247. }