svg_test.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. package svg // import "github.com/tdewolff/minify/svg"
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "os"
  7. "testing"
  8. "github.com/tdewolff/minify"
  9. "github.com/tdewolff/minify/css"
  10. "github.com/tdewolff/test"
  11. )
  12. func TestSVG(t *testing.T) {
  13. svgTests := []struct {
  14. svg string
  15. expected string
  16. }{
  17. {`<!-- comment -->`, ``},
  18. {`<!DOCTYPE svg SYSTEM "foo.dtd">`, ``},
  19. {`<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "foo.dtd" [ <!ENTITY x "bar"> ]>`, `<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "foo.dtd" [ <!ENTITY x "bar"> ]>`},
  20. {`<!DOCTYPE svg SYSTEM "foo.dtd">`, ``},
  21. {`<?xml version="1.0" ?>`, ``},
  22. {`<style> <![CDATA[ x ]]> </style>`, `<style>x</style>`},
  23. {`<style> <![CDATA[ <<<< ]]> </style>`, `<style>&lt;&lt;&lt;&lt;</style>`},
  24. {`<style> <![CDATA[ <<<<< ]]> </style>`, `<style><![CDATA[ <<<<< ]]></style>`},
  25. {`<style/><![CDATA[ <<<<< ]]>`, `<style/><![CDATA[ <<<<< ]]>`},
  26. {`<svg version="1.0"></svg>`, `<svg version="1.0"/>`},
  27. {`<svg version="1.1" x="0" y="0px" width="100%" height="100%"><path/></svg>`, `<svg><path/></svg>`},
  28. {`<path x="a"> </path>`, `<path x="a"/>`},
  29. {`<path x=" a "/>`, `<path x="a"/>`},
  30. {"<path x=\" a \n b \"/>", `<path x="a b"/>`},
  31. {`<path x="5.0px" y="0%"/>`, `<path x="5" y="0"/>`},
  32. {`<svg viewBox="5.0px 5px 240IN px"><path/></svg>`, `<svg viewBox="5 5 240in px"><path/></svg>`},
  33. {`<svg viewBox="5.0!5px"><path/></svg>`, `<svg viewBox="5!5px"><path/></svg>`},
  34. {`<path d="M 100 100 L 300 100 L 200 100 z"/>`, `<path d="M1e2 1e2H3e2 2e2z"/>`},
  35. {`<path d="M100 -100M200 300z"/>`, `<path d="M1e2-1e2M2e2 3e2z"/>`},
  36. {`<path d="M0.5 0.6 M -100 0.5z"/>`, `<path d="M.5.6M-1e2.5z"/>`},
  37. {`<path d="M01.0 0.6 z"/>`, `<path d="M1 .6z"/>`},
  38. {`<path d="M20 20l-10-10z"/>`, `<path d="M20 20 10 10z"/>`},
  39. {`<?xml version="1.0" encoding="utf-8"?>`, ``},
  40. {`<svg viewbox="0 0 16 16"><path/></svg>`, `<svg viewbox="0 0 16 16"><path/></svg>`},
  41. {`<g></g>`, ``},
  42. {`<g><path/></g>`, `<path/>`},
  43. {`<g id="a"><g><path/></g></g>`, `<g id="a"><path/></g>`},
  44. {`<path fill="#ffffff"/>`, `<path fill="#fff"/>`},
  45. {`<path fill="#fff"/>`, `<path fill="#fff"/>`},
  46. {`<path fill="white"/>`, `<path fill="#fff"/>`},
  47. {`<path fill="#ff0000"/>`, `<path fill="red"/>`},
  48. {`<line x1="5" y1="10" x2="20" y2="40"/>`, `<path d="M5 10 20 40z"/>`},
  49. {`<rect x="5" y="10" width="20" height="40"/>`, `<path d="M5 10h20v40H5z"/>`},
  50. {`<rect x="-5.669" y="147.402" fill="#843733" width="252.279" height="14.177"/>`, `<path fill="#843733" d="M-5.669 147.402h252.279v14.177H-5.669z"/>`},
  51. {`<rect x="5" y="10" rx="2" ry="3"/>`, `<rect x="5" y="10" rx="2" ry="3"/>`},
  52. {`<rect x="5" y="10" height="40"/>`, ``},
  53. {`<rect x="5" y="10" width="30" height="0"/>`, ``},
  54. {`<polygon points="1,2 3,4"/>`, `<path d="M1 2 3 4z"/>`},
  55. {`<polyline points="1,2 3,4"/>`, `<path d="M1 2 3 4"/>`},
  56. {`<svg contentStyleType="text/json ; charset=iso-8859-1"><style>{a : true}</style></svg>`, `<svg contentStyleType="text/json;charset=iso-8859-1"><style>{a : true}</style></svg>`},
  57. {`<metadata><dc:title /></metadata>`, ``},
  58. // from SVGO
  59. {`<!DOCTYPE bla><?xml?><!-- comment --><metadata/>`, ``},
  60. {`<polygon fill="none" stroke="#000" points="-0.1,"/>`, `<polygon fill="none" stroke="#000" points="-0.1,"/>`}, // #45
  61. {`<path stroke="url(#UPPERCASE)"/>`, `<path stroke="url(#UPPERCASE)"/>`}, // #117
  62. // go fuzz
  63. {`<0 d=09e9.6e-9e0`, `<0 d=""`},
  64. {`<line`, `<line`},
  65. }
  66. m := minify.New()
  67. for _, tt := range svgTests {
  68. t.Run(tt.svg, func(t *testing.T) {
  69. r := bytes.NewBufferString(tt.svg)
  70. w := &bytes.Buffer{}
  71. err := Minify(m, w, r, nil)
  72. test.Minify(t, tt.svg, err, w.String(), tt.expected)
  73. })
  74. }
  75. }
  76. func TestSVGStyle(t *testing.T) {
  77. svgTests := []struct {
  78. svg string
  79. expected string
  80. }{
  81. {`<style> a > b {} </style>`, `<style>a>b{}</style>`},
  82. {`<style> <![CDATA[ @media x < y {} ]]> </style>`, `<style>@media x &lt; y{}</style>`},
  83. {`<style> <![CDATA[ * { content: '<<<<<'; } ]]> </style>`, `<style><![CDATA[*{content:'<<<<<'}]]></style>`},
  84. {`<style/><![CDATA[ * { content: '<<<<<'; ]]>`, `<style/><![CDATA[ * { content: '<<<<<'; ]]>`},
  85. {`<path style="fill: black; stroke: #ff0000;"/>`, `<path style="fill:#000;stroke:red"/>`},
  86. }
  87. m := minify.New()
  88. m.AddFunc("text/css", css.Minify)
  89. for _, tt := range svgTests {
  90. t.Run(tt.svg, func(t *testing.T) {
  91. r := bytes.NewBufferString(tt.svg)
  92. w := &bytes.Buffer{}
  93. err := Minify(m, w, r, nil)
  94. test.Minify(t, tt.svg, err, w.String(), tt.expected)
  95. })
  96. }
  97. }
  98. func TestSVGDecimals(t *testing.T) {
  99. var svgTests = []struct {
  100. svg string
  101. expected string
  102. }{
  103. {`<svg x="1.234" y="0.001" width="1.001"><path/></svg>`, `<svg x="1.2" width="1"><path/></svg>`},
  104. }
  105. m := minify.New()
  106. o := &Minifier{Decimals: 1}
  107. for _, tt := range svgTests {
  108. t.Run(tt.svg, func(t *testing.T) {
  109. r := bytes.NewBufferString(tt.svg)
  110. w := &bytes.Buffer{}
  111. err := o.Minify(m, w, r, nil)
  112. test.Minify(t, tt.svg, err, w.String(), tt.expected)
  113. })
  114. }
  115. }
  116. func TestReaderErrors(t *testing.T) {
  117. r := test.NewErrorReader(0)
  118. w := &bytes.Buffer{}
  119. m := minify.New()
  120. err := Minify(m, w, r, nil)
  121. test.T(t, err, test.ErrPlain, "return error at first read")
  122. }
  123. func TestWriterErrors(t *testing.T) {
  124. errorTests := []struct {
  125. svg string
  126. n []int
  127. }{
  128. {`<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "foo.dtd" [ <!ENTITY x "bar"> ]>`, []int{0}},
  129. {`abc`, []int{0}},
  130. {`<style>abc</style>`, []int{2}},
  131. {`<![CDATA[ <<<< ]]>`, []int{0}},
  132. {`<![CDATA[ <<<<< ]]>`, []int{0}},
  133. {`<path d="x"/>`, []int{0, 1, 2, 3, 4, 5}},
  134. {`<path></path>`, []int{1}},
  135. {`<svg>x</svg>`, []int{1, 3}},
  136. {`<svg>x</svg >`, []int{3}},
  137. }
  138. m := minify.New()
  139. for _, tt := range errorTests {
  140. for _, n := range tt.n {
  141. t.Run(fmt.Sprint(tt.svg, " ", tt.n), func(t *testing.T) {
  142. r := bytes.NewBufferString(tt.svg)
  143. w := test.NewErrorWriter(n)
  144. err := Minify(m, w, r, nil)
  145. test.T(t, err, test.ErrPlain)
  146. })
  147. }
  148. }
  149. }
  150. func TestMinifyErrors(t *testing.T) {
  151. errorTests := []struct {
  152. svg string
  153. err error
  154. }{
  155. {`<style>abc</style>`, test.ErrPlain},
  156. {`<style><![CDATA[abc]]></style>`, test.ErrPlain},
  157. {`<path style="abc"/>`, test.ErrPlain},
  158. }
  159. m := minify.New()
  160. m.AddFunc("text/css", func(_ *minify.M, w io.Writer, r io.Reader, _ map[string]string) error {
  161. return test.ErrPlain
  162. })
  163. for _, tt := range errorTests {
  164. t.Run(tt.svg, func(t *testing.T) {
  165. r := bytes.NewBufferString(tt.svg)
  166. w := &bytes.Buffer{}
  167. err := Minify(m, w, r, nil)
  168. test.T(t, err, tt.err)
  169. })
  170. }
  171. }
  172. ////////////////////////////////////////////////////////////////
  173. func ExampleMinify() {
  174. m := minify.New()
  175. m.AddFunc("image/svg+xml", Minify)
  176. m.AddFunc("text/css", css.Minify)
  177. if err := m.Minify("image/svg+xml", os.Stdout, os.Stdin); err != nil {
  178. panic(err)
  179. }
  180. }