git.greeks.studio/lethews/hugo@v0.47.1/parser/parse_frontmatter_test.go (about)

     1  // Copyright 2015 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package parser
    15  
    16  // TODO Support Mac Encoding (\r)
    17  
    18  import (
    19  	"bufio"
    20  	"bytes"
    21  	"io"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  	"testing"
    26  )
    27  
    28  const (
    29  	contentNoFrontmatter                        = "a page with no front matter"
    30  	contentWithFrontmatter                      = "---\ntitle: front matter\n---\nContent with front matter"
    31  	contentHTMLNoDoctype                        = "<html>\n\t<body>\n\t</body>\n</html>"
    32  	contentHTMLWithDoctype                      = "<!doctype html><html><body></body></html>"
    33  	contentHTMLWithFrontmatter                  = "---\ntitle: front matter\n---\n<!doctype><html><body></body></html>"
    34  	contentHTML                                 = "    <html><body></body></html>"
    35  	contentLinefeedAndHTML                      = "\n<html><body></body></html>"
    36  	contentIncompleteEndFrontmatterDelim        = "---\ntitle: incomplete end fm delim\n--\nincomplete frontmatter delim"
    37  	contentMissingEndFrontmatterDelim           = "---\ntitle: incomplete end fm delim\nincomplete frontmatter delim"
    38  	contentSlugWorking                          = "---\ntitle: slug doc 2\nslug: slug-doc-2\n\n---\nslug doc 2 content"
    39  	contentSlugWorkingVariation                 = "---\ntitle: slug doc 3\nslug: slug-doc 3\n---\nslug doc 3 content"
    40  	contentSlugBug                              = "---\ntitle: slug doc 2\nslug: slug-doc-2\n---\nslug doc 2 content"
    41  	contentSlugWithJSONFrontMatter              = "{\n  \"categories\": \"d\",\n  \"tags\": [\n    \"a\", \n    \"b\", \n    \"c\"\n  ]\n}\nJSON Front Matter with tags and categories"
    42  	contentWithJSONLooseFrontmatter             = "{\n  \"categories\": \"d\"\n  \"tags\": [\n    \"a\" \n    \"b\" \n    \"c\"\n  ]\n}\nJSON Front Matter with tags and categories"
    43  	contentSlugWithJSONFrontMatterFirstLineOnly = "{\"categories\":\"d\",\"tags\":[\"a\",\"b\",\"c\"]}\nJSON Front Matter with tags and categories"
    44  	contentSlugWithJSONFrontMatterFirstLine     = "{\"categories\":\"d\",\n  \"tags\":[\"a\",\"b\",\"c\"]}\nJSON Front Matter with tags and categories"
    45  )
    46  
    47  var lineEndings = []string{"\n", "\r\n"}
    48  var delimiters = []string{"---", "+++"}
    49  
    50  func pageMust(p Page, err error) *page {
    51  	if err != nil {
    52  		panic(err)
    53  	}
    54  	return p.(*page)
    55  }
    56  
    57  func TestDegenerateCreatePageFrom(t *testing.T) {
    58  	tests := []struct {
    59  		content string
    60  	}{
    61  		{contentMissingEndFrontmatterDelim},
    62  		{contentIncompleteEndFrontmatterDelim},
    63  	}
    64  
    65  	for _, test := range tests {
    66  		for _, ending := range lineEndings {
    67  			test.content = strings.Replace(test.content, "\n", ending, -1)
    68  			_, err := ReadFrom(strings.NewReader(test.content))
    69  			if err == nil {
    70  				t.Errorf("Content should return an err:\n%q\n", test.content)
    71  			}
    72  		}
    73  	}
    74  }
    75  
    76  func checkPageRender(t *testing.T, p *page, expected bool) {
    77  	if p.render != expected {
    78  		t.Errorf("page.render should be %t, got: %t", expected, p.render)
    79  	}
    80  }
    81  
    82  func checkPageFrontMatterIsNil(t *testing.T, p *page, content string, expected bool) {
    83  	if bool(p.frontmatter == nil) != expected {
    84  		t.Logf("\n%q\n", content)
    85  		t.Errorf("page.frontmatter == nil? %t, got %t", expected, p.frontmatter == nil)
    86  	}
    87  }
    88  
    89  func checkPageFrontMatterContent(t *testing.T, p *page, frontMatter string) {
    90  	if p.frontmatter == nil {
    91  		return
    92  	}
    93  	if !bytes.Equal(p.frontmatter, []byte(frontMatter)) {
    94  		t.Errorf("frontmatter mismatch\nexp: %q\ngot: %q", frontMatter, p.frontmatter)
    95  	}
    96  }
    97  
    98  func checkPageContent(t *testing.T, p *page, expected string) {
    99  	if !bytes.Equal(p.content, []byte(expected)) {
   100  		t.Errorf("content mismatch\nexp: %q\ngot: %q", expected, p.content)
   101  	}
   102  }
   103  
   104  func TestStandaloneCreatePageFrom(t *testing.T) {
   105  	tests := []struct {
   106  		content            string
   107  		expectedMustRender bool
   108  		frontMatterIsNil   bool
   109  		frontMatter        string
   110  		bodycontent        string
   111  	}{
   112  
   113  		{contentNoFrontmatter, true, true, "", "a page with no front matter"},
   114  		{contentWithFrontmatter, true, false, "---\ntitle: front matter\n---\n", "Content with front matter"},
   115  		{contentHTMLNoDoctype, false, true, "", "<html>\n\t<body>\n\t</body>\n</html>"},
   116  		{contentHTMLWithDoctype, false, true, "", "<!doctype html><html><body></body></html>"},
   117  		{contentHTMLWithFrontmatter, true, false, "---\ntitle: front matter\n---\n", "<!doctype><html><body></body></html>"},
   118  		{contentHTML, false, true, "", "<html><body></body></html>"},
   119  		{contentLinefeedAndHTML, false, true, "", "<html><body></body></html>"},
   120  		{contentSlugWithJSONFrontMatter, true, false, "{\n  \"categories\": \"d\",\n  \"tags\": [\n    \"a\", \n    \"b\", \n    \"c\"\n  ]\n}", "JSON Front Matter with tags and categories"},
   121  		{contentWithJSONLooseFrontmatter, true, false, "{\n  \"categories\": \"d\"\n  \"tags\": [\n    \"a\" \n    \"b\" \n    \"c\"\n  ]\n}", "JSON Front Matter with tags and categories"},
   122  		{contentSlugWithJSONFrontMatterFirstLineOnly, true, false, "{\"categories\":\"d\",\"tags\":[\"a\",\"b\",\"c\"]}", "JSON Front Matter with tags and categories"},
   123  		{contentSlugWithJSONFrontMatterFirstLine, true, false, "{\"categories\":\"d\",\n  \"tags\":[\"a\",\"b\",\"c\"]}", "JSON Front Matter with tags and categories"},
   124  		{contentSlugWorking, true, false, "---\ntitle: slug doc 2\nslug: slug-doc-2\n\n---\n", "slug doc 2 content"},
   125  		{contentSlugWorkingVariation, true, false, "---\ntitle: slug doc 3\nslug: slug-doc 3\n---\n", "slug doc 3 content"},
   126  		{contentSlugBug, true, false, "---\ntitle: slug doc 2\nslug: slug-doc-2\n---\n", "slug doc 2 content"},
   127  	}
   128  
   129  	for _, test := range tests {
   130  		for _, ending := range lineEndings {
   131  			test.content = strings.Replace(test.content, "\n", ending, -1)
   132  			test.frontMatter = strings.Replace(test.frontMatter, "\n", ending, -1)
   133  			test.bodycontent = strings.Replace(test.bodycontent, "\n", ending, -1)
   134  
   135  			p := pageMust(ReadFrom(strings.NewReader(test.content)))
   136  
   137  			checkPageRender(t, p, test.expectedMustRender)
   138  			checkPageFrontMatterIsNil(t, p, test.content, test.frontMatterIsNil)
   139  			checkPageFrontMatterContent(t, p, test.frontMatter)
   140  			checkPageContent(t, p, test.bodycontent)
   141  		}
   142  	}
   143  }
   144  
   145  func BenchmarkLongFormRender(b *testing.B) {
   146  
   147  	tests := []struct {
   148  		filename string
   149  		buf      []byte
   150  	}{
   151  		{filename: "long_text_test.md"},
   152  	}
   153  	for i, test := range tests {
   154  		path := filepath.FromSlash(test.filename)
   155  		f, err := os.Open(path)
   156  		if err != nil {
   157  			b.Fatalf("Unable to open %s: %s", path, err)
   158  		}
   159  		defer f.Close()
   160  		membuf := new(bytes.Buffer)
   161  		if _, err := io.Copy(membuf, f); err != nil {
   162  			b.Fatalf("Unable to read %s: %s", path, err)
   163  		}
   164  		tests[i].buf = membuf.Bytes()
   165  	}
   166  
   167  	b.ResetTimer()
   168  
   169  	for i := 0; i <= b.N; i++ {
   170  		for _, test := range tests {
   171  			ReadFrom(bytes.NewReader(test.buf))
   172  		}
   173  	}
   174  }
   175  
   176  func TestPageShouldRender(t *testing.T) {
   177  	tests := []struct {
   178  		content  []byte
   179  		expected bool
   180  	}{
   181  		{[]byte{}, false},
   182  		{[]byte{'<'}, false},
   183  		{[]byte{'-'}, true},
   184  		{[]byte("--"), true},
   185  		{[]byte("---"), true},
   186  		{[]byte("---\n"), true},
   187  		{[]byte{'a'}, true},
   188  	}
   189  
   190  	for _, test := range tests {
   191  		for _, ending := range lineEndings {
   192  			test.content = bytes.Replace(test.content, []byte("\n"), []byte(ending), -1)
   193  			if render := shouldRender(test.content); render != test.expected {
   194  
   195  				t.Errorf("Expected %s to shouldRender = %t, got: %t", test.content, test.expected, render)
   196  			}
   197  		}
   198  	}
   199  }
   200  
   201  func TestPageHasFrontMatter(t *testing.T) {
   202  	tests := []struct {
   203  		content  []byte
   204  		expected bool
   205  	}{
   206  		{[]byte{'-'}, false},
   207  		{[]byte("--"), false},
   208  		{[]byte("---"), false},
   209  		{[]byte("---\n"), true},
   210  		{[]byte("---\n"), true},
   211  		{[]byte("--- \n"), true},
   212  		{[]byte("---  \n"), true},
   213  		{[]byte{'a'}, false},
   214  		{[]byte{'{'}, true},
   215  		{[]byte("{\n  "), true},
   216  		{[]byte{'}'}, false},
   217  	}
   218  	for _, test := range tests {
   219  		for _, ending := range lineEndings {
   220  			test.content = bytes.Replace(test.content, []byte("\n"), []byte(ending), -1)
   221  			if isFrontMatterDelim := isFrontMatterDelim(test.content); isFrontMatterDelim != test.expected {
   222  				t.Errorf("Expected %q isFrontMatterDelim = %t,  got: %t", test.content, test.expected, isFrontMatterDelim)
   223  			}
   224  		}
   225  	}
   226  }
   227  
   228  func TestExtractFrontMatter(t *testing.T) {
   229  
   230  	tests := []struct {
   231  		frontmatter string
   232  		extracted   []byte
   233  		errIsNil    bool
   234  	}{
   235  		{"", nil, false},
   236  		{"-", nil, false},
   237  		{"---\n", nil, false},
   238  		{"---\nfoobar", nil, false},
   239  		{"---\nfoobar\nbarfoo\nfizbaz\n", nil, false},
   240  		{"---\nblar\n-\n", nil, false},
   241  		{"---\nralb\n---\n", []byte("---\nralb\n---\n"), true},
   242  		{"---\neof\n---", []byte("---\neof\n---"), true},
   243  		{"--- \neof\n---", []byte("---\neof\n---"), true},
   244  		{"---\nminc\n---\ncontent", []byte("---\nminc\n---\n"), true},
   245  		{"---\nminc\n---    \ncontent", []byte("---\nminc\n---\n"), true},
   246  		{"---  \nminc\n--- \ncontent", []byte("---\nminc\n---\n"), true},
   247  		{"---\ncnim\n---\ncontent\n", []byte("---\ncnim\n---\n"), true},
   248  		{"---\ntitle: slug doc 2\nslug: slug-doc-2\n---\ncontent\n", []byte("---\ntitle: slug doc 2\nslug: slug-doc-2\n---\n"), true},
   249  		{"---\npermalink: '/blog/title---subtitle.html'\n---\ncontent\n", []byte("---\npermalink: '/blog/title---subtitle.html'\n---\n"), true},
   250  	}
   251  
   252  	for _, test := range tests {
   253  		for _, ending := range lineEndings {
   254  			test.frontmatter = strings.Replace(test.frontmatter, "\n", ending, -1)
   255  			test.extracted = bytes.Replace(test.extracted, []byte("\n"), []byte(ending), -1)
   256  			for _, delim := range delimiters {
   257  				test.frontmatter = strings.Replace(test.frontmatter, "---", delim, -1)
   258  				test.extracted = bytes.Replace(test.extracted, []byte("---"), []byte(delim), -1)
   259  				line, err := peekLine(bufio.NewReader(strings.NewReader(test.frontmatter)))
   260  				if err != nil {
   261  					continue
   262  				}
   263  				l, r := determineDelims(line)
   264  				fm, err := extractFrontMatterDelims(bufio.NewReader(strings.NewReader(test.frontmatter)), l, r)
   265  				if (err == nil) != test.errIsNil {
   266  					t.Logf("\n%q\n", string(test.frontmatter))
   267  					t.Errorf("Expected err == nil => %t, got: %t. err: %s", test.errIsNil, err == nil, err)
   268  					continue
   269  				}
   270  				if !bytes.Equal(fm, test.extracted) {
   271  					t.Errorf("Frontmatter did not match:\nexp: %q\ngot: %q", string(test.extracted), fm)
   272  				}
   273  			}
   274  		}
   275  	}
   276  }
   277  
   278  func TestExtractFrontMatterDelim(t *testing.T) {
   279  	var (
   280  		noErrExpected = true
   281  		errExpected   = false
   282  	)
   283  	tests := []struct {
   284  		frontmatter string
   285  		extracted   string
   286  		errIsNil    bool
   287  	}{
   288  		{"", "", errExpected},
   289  		{"{", "", errExpected},
   290  		{"{}", "{}", noErrExpected},
   291  		{"{} ", "{}", noErrExpected},
   292  		{"{ } ", "{ }", noErrExpected},
   293  		{"{ { }", "", errExpected},
   294  		{"{ { } }", "{ { } }", noErrExpected},
   295  		{"{ { } { } }", "{ { } { } }", noErrExpected},
   296  		{"{\n{\n}\n}\n", "{\n{\n}\n}", noErrExpected},
   297  		{"{\n  \"categories\": \"d\",\n  \"tags\": [\n    \"a\", \n    \"b\", \n    \"c\"\n  ]\n}\nJSON Front Matter with tags and categories", "{\n  \"categories\": \"d\",\n  \"tags\": [\n    \"a\", \n    \"b\", \n    \"c\"\n  ]\n}", noErrExpected},
   298  		{"{\n  \"categories\": \"d\"\n  \"tags\": [\n    \"a\" \n    \"b\" \n    \"c\"\n  ]\n}\nJSON Front Matter with tags and categories", "{\n  \"categories\": \"d\"\n  \"tags\": [\n    \"a\" \n    \"b\" \n    \"c\"\n  ]\n}", noErrExpected},
   299  		// Issue #3511
   300  		{`{ "title": "{" }`, `{ "title": "{" }`, noErrExpected},
   301  		{`{ "title": "{}" }`, `{ "title": "{}" }`, noErrExpected},
   302  		// Issue #3661
   303  		{`{ "title": "\"" }`, `{ "title": "\"" }`, noErrExpected},
   304  		{`{ "title": "\"{", "other": "\"{}" }`, `{ "title": "\"{", "other": "\"{}" }`, noErrExpected},
   305  		{`{ "title": "\"Foo\"" }`, `{ "title": "\"Foo\"" }`, noErrExpected},
   306  		{`{ "title": "\"Foo\"\"" }`, `{ "title": "\"Foo\"\"" }`, noErrExpected},
   307  		{`{ "url": "http:\/\/example.com\/play\/url?id=1" }`, `{ "url": "http:\/\/example.com\/play\/url?id=1" }`, noErrExpected},
   308  		{`{ "test": "\"New\r\nString\"" }`, `{ "test": "\"New\r\nString\"" }`, noErrExpected},
   309  		{`{ "test": "RTS\/RPG" }`, `{ "test": "RTS\/RPG" }`, noErrExpected},
   310  	}
   311  
   312  	for i, test := range tests {
   313  		fm, err := extractFrontMatterDelims(bufio.NewReader(strings.NewReader(test.frontmatter)), []byte("{"), []byte("}"))
   314  		if (err == nil) != test.errIsNil {
   315  			t.Logf("\n%q\n", string(test.frontmatter))
   316  			t.Errorf("[%d] Expected err == nil => %t, got: %t. err: %s", i, test.errIsNil, err == nil, err)
   317  			continue
   318  		}
   319  		if !bytes.Equal(fm, []byte(test.extracted)) {
   320  			t.Logf("\n%q\n", string(test.frontmatter))
   321  			t.Errorf("[%d] Frontmatter did not match:\nexp: %q\ngot:  %q", i, string(test.extracted), fm)
   322  		}
   323  	}
   324  }