hugo/minifiers/minifiers_test.go

221 lines
6.7 KiB
Go
Raw Normal View History

// Copyright 2018 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package minifiers
2013-10-01 15:59:29 -04:00
import (
"bytes"
"encoding/json"
"strings"
"testing"
2013-10-01 15:59:29 -04:00
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/output"
"github.com/tdewolff/minify/v2/html"
)
func TestNew(t *testing.T) {
c := qt.New(t)
v := config.New()
m, _ := New(media.DefaultTypes, output.DefaultFormats, v)
var rawJS string
var minJS string
rawJS = " var foo =1 ; foo ++ ; "
minJS = "var foo=1;foo++"
var rawJSON string
var minJSON string
rawJSON = " { \"a\" : 123 , \"b\":2, \"c\": 5 } "
minJSON = "{\"a\":123,\"b\":2,\"c\":5}"
for _, test := range []struct {
tp media.Type
rawString string
expectedMinString string
}{
{media.CSSType, " body { color: blue; } ", "body{color:blue}"},
{media.RSSType, " <hello> Hugo! </hello> ", "<hello>Hugo!</hello>"}, // RSS should be handled as XML
{media.JSONType, rawJSON, minJSON},
{media.JavascriptType, rawJS, minJS},
// JS Regex minifiers
{media.Type{MainType: "application", SubType: "ecmascript"}, rawJS, minJS},
{media.Type{MainType: "application", SubType: "javascript"}, rawJS, minJS},
{media.Type{MainType: "application", SubType: "x-javascript"}, rawJS, minJS},
{media.Type{MainType: "application", SubType: "x-ecmascript"}, rawJS, minJS},
{media.Type{MainType: "text", SubType: "ecmascript"}, rawJS, minJS},
{media.Type{MainType: "text", SubType: "javascript"}, rawJS, minJS},
{media.Type{MainType: "text", SubType: "x-javascript"}, rawJS, minJS},
{media.Type{MainType: "text", SubType: "x-ecmascript"}, rawJS, minJS},
// JSON Regex minifiers
{media.Type{MainType: "application", SubType: "json"}, rawJSON, minJSON},
{media.Type{MainType: "application", SubType: "x-json"}, rawJSON, minJSON},
{media.Type{MainType: "application", SubType: "ld+json"}, rawJSON, minJSON},
{media.Type{MainType: "text", SubType: "json"}, rawJSON, minJSON},
{media.Type{MainType: "text", SubType: "x-json"}, rawJSON, minJSON},
{media.Type{MainType: "text", SubType: "ld+json"}, rawJSON, minJSON},
} {
var b bytes.Buffer
c.Assert(m.Minify(test.tp, &b, strings.NewReader(test.rawString)), qt.IsNil)
c.Assert(b.String(), qt.Equals, test.expectedMinString)
}
}
2020-03-20 11:34:53 -04:00
func TestConfigureMinify(t *testing.T) {
c := qt.New(t)
v := config.New()
v.Set("minify", map[string]any{
2020-03-20 11:34:53 -04:00
"disablexml": true,
"tdewolff": map[string]any{
"html": map[string]any{
"keepwhitespace": true,
},
},
})
m, _ := New(media.DefaultTypes, output.DefaultFormats, v)
for _, test := range []struct {
tp media.Type
rawString string
expectedMinString string
errorExpected bool
}{
Pass minification errors to the user Previously, *minifyTransformation.Transform suppressed the error returned by t.m.Minify. This meant that when minification returned an error, the error would not reach the user. Instead, minification would silently fail. For example, if a JavaScript file included a call to the Date constructor with: new Date(2020, 04, 02) The package that the minification library uses to parse JS files, github.com/tdewolff/parse would return an error, since "04" would be parsed as a legacy octal. However, the JS file would remain un-minified with no error. Fixing this is not as simple as replacing "_" with an "err" in *minifyTransformation.Transform, however (though this is necessary). If we only returned this error from Transform, then hugolib.TestResourceMinifyDisabled would fail. Instead of being a no-op, as TestResourceMinifyDisabled expects, using the "minify" template function with a "disableXML=true" config setting instead returns the error, "minifier does not exist for mimetype." The "minifier does not exist" error is returned because of the way minifiers.New works. If the user's config disables minification for a particular MIME type, minifiers.New does not add it to the resulting Client's *minify.M. However, this also means that when the "minify" template function is executed, a *resourceAdapter's transformations still add a minification. When it comes time to call the minify.Minifier for a specific MIME type via *M.MinifyMimetype, the github.com/tdewolff/minify library throws the "does not exist" error for the missing MIME type. The solution was to change minifiers.New so, instead of skipping a minifier for each disabled MIME type, it adds a NoOpMinifier, which simply copies the source to the destination without minification. This means that when the "minify" template function is used for a particular resource, and that resource's MIME type has minification disabled, minification is genuinely skipped, and does not result in an error. In order to add this, I've fixed a possibly unwanted interaction between minifiers.TestConfigureMinify and hugolib.TestResourceMinifyDisabled. The latter disables minification and expects minification to be a no-op. The former disables minification and expects it to result in an error. The only reason hugolib.TestResourceMinifyDisabled passes in the original code is that the "does not exist" error is suppressed. However, we shouldn't suppress minification errors, since they can leave users perplexed. I've changed the test assertion in minifiers.TestConfigureMinify to expect no errors and a no-op if minification is disabled for a particular MIME type. Fixes #8954
2021-09-22 14:54:40 -04:00
{media.HTMLType, "<hello> Hugo! </hello>", "<hello> Hugo! </hello>", false}, // configured minifier
{media.CSSType, " body { color: blue; } ", "body{color:blue}", false}, // default minifier
{media.XMLType, " <hello> Hugo! </hello> ", " <hello> Hugo! </hello> ", false}, // disable Xml minification
} {
var b bytes.Buffer
if !test.errorExpected {
c.Assert(m.Minify(test.tp, &b, strings.NewReader(test.rawString)), qt.IsNil)
c.Assert(b.String(), qt.Equals, test.expectedMinString)
} else {
err := m.Minify(test.tp, &b, strings.NewReader(test.rawString))
c.Assert(err, qt.ErrorMatches, "minifier does not exist for mimetype")
}
}
}
func TestJSONRoundTrip(t *testing.T) {
c := qt.New(t)
v := config.New()
m, _ := New(media.DefaultTypes, output.DefaultFormats, v)
for _, test := range []string{`{
"glossary": {
"title": "example glossary",
"GlossDiv": {
"title": "S",
"GlossList": {
"GlossEntry": {
"ID": "SGML",
"SortAs": "SGML",
"GlossTerm": "Standard Generalized Markup Language",
"Acronym": "SGML",
"Abbrev": "ISO 8879:1986",
"GlossDef": {
"para": "A meta-markup language, used to create markup languages such as DocBook.",
"GlossSeeAlso": ["GML", "XML"]
},
"GlossSee": "markup"
}
}
}
}
}`} {
var b bytes.Buffer
m1 := make(map[string]any)
m2 := make(map[string]any)
c.Assert(json.Unmarshal([]byte(test), &m1), qt.IsNil)
c.Assert(m.Minify(media.JSONType, &b, strings.NewReader(test)), qt.IsNil)
c.Assert(json.Unmarshal(b.Bytes(), &m2), qt.IsNil)
c.Assert(m1, qt.DeepEquals, m2)
}
}
func TestBugs(t *testing.T) {
c := qt.New(t)
v := config.New()
m, _ := New(media.DefaultTypes, output.DefaultFormats, v)
for _, test := range []struct {
tp media.Type
rawString string
expectedMinString string
}{
// https://github.com/gohugoio/hugo/issues/5506
{media.CSSType, " body { color: rgba(000, 000, 000, 0.7); }", "body{color:rgba(0,0,0,.7)}"},
// https://github.com/gohugoio/hugo/issues/8332
{media.HTMLType, "<i class='fas fa-tags fa-fw'></i> Tags", `<i class="fas fa-tags fa-fw"></i> Tags`},
} {
var b bytes.Buffer
c.Assert(m.Minify(test.tp, &b, strings.NewReader(test.rawString)), qt.IsNil)
c.Assert(b.String(), qt.Equals, test.expectedMinString)
}
}
// Renamed to Precision in v2.7.0. Check that we support both.
func TestDecodeConfigDecimalIsNowPrecision(t *testing.T) {
c := qt.New(t)
v := config.New()
v.Set("minify", map[string]any{
"disablexml": true,
"tdewolff": map[string]any{
"css": map[string]any{
"decimal": 3,
},
"svg": map[string]any{
"decimal": 3,
},
},
})
conf, err := decodeConfig(v)
c.Assert(err, qt.IsNil)
c.Assert(conf.Tdewolff.CSS.Precision, qt.Equals, 3)
}
// Issue 9456
func TestDecodeConfigKeepWhitespace(t *testing.T) {
c := qt.New(t)
v := config.New()
v.Set("minify", map[string]any{
"tdewolff": map[string]any{
"html": map[string]any{
"keepEndTags": false,
},
},
})
conf, err := decodeConfig(v)
c.Assert(err, qt.IsNil)
c.Assert(conf.Tdewolff.HTML, qt.DeepEquals,
html.Minifier{
KeepComments: false,
KeepConditionalComments: true,
KeepDefaultAttrVals: true,
KeepDocumentTags: true,
KeepEndTags: false,
KeepQuotes: false,
KeepWhitespace: false},
)
}