package parser // TODO Support Mac Encoding (\r) import ( "bufio" "bytes" "io" "os" "path/filepath" "strings" "testing" ) var ( CONTENT_EMPTY = "" CONTENT_NO_FRONTMATTER = "a page with no front matter" CONTENT_WITH_FRONTMATTER = "---\ntitle: front matter\n---\nContent with front matter" CONTENT_HTML_NODOCTYPE = "\n\t\n\t\n" CONTENT_HTML_WITHDOCTYPE = "" CONTENT_HTML_WITH_FRONTMATTER = "---\ntitle: front matter\n---\n" CONTENT_LWS_HTML = " " CONTENT_LWS_LF_HTML = "\n" CONTENT_INCOMPLETE_BEG_FM_DELIM = "--\ntitle: incomplete beg fm delim\n---\nincomplete frontmatter delim" CONTENT_INCOMPLETE_END_FM_DELIM = "---\ntitle: incomplete end fm delim\n--\nincomplete frontmatter delim" CONTENT_MISSING_END_FM_DELIM = "---\ntitle: incomplete end fm delim\nincomplete frontmatter delim" CONTENT_SLUG_WORKING = "---\ntitle: slug doc 2\nslug: slug-doc-2\n\n---\nslug doc 2 content" CONTENT_SLUG_WORKING_VARIATION = "---\ntitle: slug doc 3\nslug: slug-doc 3\n---\nslug doc 3 content" CONTENT_SLUG_BUG = "---\ntitle: slug doc 2\nslug: slug-doc-2\n---\nslug doc 2 content" CONTENT_FM_NO_DOC = "---\ntitle: no doc\n---" CONTENT_WITH_JS_FM = "{\n \"categories\": \"d\",\n \"tags\": [\n \"a\", \n \"b\", \n \"c\"\n ]\n}\nJSON Front Matter with tags and categories" CONTENT_WITH_JS_LOOSE_FM = "{\n \"categories\": \"d\"\n \"tags\": [\n \"a\" \n \"b\" \n \"c\"\n ]\n}\nJSON Front Matter with tags and categories" ) var lineEndings = []string{"\n", "\r\n"} var delimiters = []string{"---", "+++"} func pageMust(p Page, err error) *page { if err != nil { panic(err) } return p.(*page) } func pageRecoverAndLog(t *testing.T) { if err := recover(); err != nil { t.Errorf("panic/recover: %s\n", err) } } func TestDegenerateCreatePageFrom(t *testing.T) { tests := []struct { content string }{ {CONTENT_MISSING_END_FM_DELIM}, {CONTENT_INCOMPLETE_END_FM_DELIM}, } for _, test := range tests { for _, ending := range lineEndings { test.content = strings.Replace(test.content, "\n", ending, -1) _, err := ReadFrom(strings.NewReader(test.content)) if err == nil { t.Errorf("Content should return an err:\n%q\n", test.content) } } } } func checkPageRender(t *testing.T, p *page, expected bool) { if p.render != expected { t.Errorf("page.render should be %t, got: %t", expected, p.render) } } func checkPageFrontMatterIsNil(t *testing.T, p *page, content string, expected bool) { if bool(p.frontmatter == nil) != expected { t.Logf("\n%q\n", content) t.Errorf("page.frontmatter == nil? %t, got %t", expected, p.frontmatter == nil) } } func checkPageFrontMatterContent(t *testing.T, p *page, frontMatter string) { if p.frontmatter == nil { return } if !bytes.Equal(p.frontmatter, []byte(frontMatter)) { t.Errorf("frontmatter mismatch\nexp: %q\ngot: %q", frontMatter, p.frontmatter) } } func checkPageContent(t *testing.T, p *page, expected string) { if !bytes.Equal(p.content, []byte(expected)) { t.Errorf("content mismatch\nexp: %q\ngot: %q", expected, p.content) } } func TestStandaloneCreatePageFrom(t *testing.T) { tests := []struct { content string expectedMustRender bool frontMatterIsNil bool frontMatter string bodycontent string }{ {CONTENT_NO_FRONTMATTER, true, true, "", "a page with no front matter"}, {CONTENT_WITH_FRONTMATTER, true, false, "---\ntitle: front matter\n---\n", "Content with front matter"}, {CONTENT_HTML_NODOCTYPE, false, true, "", "\n\t\n\t\n"}, {CONTENT_HTML_WITHDOCTYPE, false, true, "", ""}, {CONTENT_HTML_WITH_FRONTMATTER, true, false, "---\ntitle: front matter\n---\n", ""}, {CONTENT_LWS_HTML, false, true, "", ""}, {CONTENT_LWS_LF_HTML, false, true, "", ""}, {CONTENT_WITH_JS_FM, true, false, "{\n \"categories\": \"d\",\n \"tags\": [\n \"a\", \n \"b\", \n \"c\"\n ]\n}", "JSON Front Matter with tags and categories"}, {CONTENT_WITH_JS_LOOSE_FM, true, false, "{\n \"categories\": \"d\"\n \"tags\": [\n \"a\" \n \"b\" \n \"c\"\n ]\n}", "JSON Front Matter with tags and categories"}, {CONTENT_SLUG_WORKING, true, false, "---\ntitle: slug doc 2\nslug: slug-doc-2\n\n---\n", "slug doc 2 content"}, {CONTENT_SLUG_WORKING_VARIATION, true, false, "---\ntitle: slug doc 3\nslug: slug-doc 3\n---\n", "slug doc 3 content"}, {CONTENT_SLUG_BUG, true, false, "---\ntitle: slug doc 2\nslug: slug-doc-2\n---\n", "slug doc 2 content"}, } for _, test := range tests { for _, ending := range lineEndings { test.content = strings.Replace(test.content, "\n", ending, -1) test.frontMatter = strings.Replace(test.frontMatter, "\n", ending, -1) test.bodycontent = strings.Replace(test.bodycontent, "\n", ending, -1) p := pageMust(ReadFrom(strings.NewReader(test.content))) checkPageRender(t, p, test.expectedMustRender) checkPageFrontMatterIsNil(t, p, test.content, test.frontMatterIsNil) checkPageFrontMatterContent(t, p, test.frontMatter) checkPageContent(t, p, test.bodycontent) } } } func BenchmarkLongFormRender(b *testing.B) { tests := []struct { filename string buf []byte }{ {filename: "long_text_test.md"}, } for i, test := range tests { path := filepath.FromSlash(test.filename) f, err := os.Open(path) if err != nil { b.Fatalf("Unable to open %s: %s", path, err) } defer f.Close() membuf := new(bytes.Buffer) if _, err := io.Copy(membuf, f); err != nil { b.Fatalf("Unable to read %s: %s", path, err) } tests[i].buf = membuf.Bytes() } b.ResetTimer() for i := 0; i <= b.N; i++ { for _, test := range tests { ReadFrom(bytes.NewReader(test.buf)) } } } func TestPageShouldRender(t *testing.T) { tests := []struct { content []byte expected bool }{ {[]byte{}, false}, {[]byte{'<'}, false}, {[]byte{'-'}, true}, {[]byte("--"), true}, {[]byte("---"), true}, {[]byte("---\n"), true}, {[]byte{'a'}, true}, } for _, test := range tests { for _, ending := range lineEndings { test.content = bytes.Replace(test.content, []byte("\n"), []byte(ending), -1) if render := shouldRender(test.content); render != test.expected { t.Errorf("Expected %s to shouldRender = %t, got: %t", test.content, test.expected, render) } } } } func TestPageHasFrontMatter(t *testing.T) { tests := []struct { content []byte expected bool }{ {[]byte{'-'}, false}, {[]byte("--"), false}, {[]byte("---"), false}, {[]byte("---\n"), true}, {[]byte("---\n"), true}, {[]byte("--- \n"), true}, {[]byte("--- \n"), true}, {[]byte{'a'}, false}, {[]byte{'{'}, true}, {[]byte("{\n "), true}, {[]byte{'}'}, false}, } for _, test := range tests { for _, ending := range lineEndings { test.content = bytes.Replace(test.content, []byte("\n"), []byte(ending), -1) if isFrontMatterDelim := isFrontMatterDelim(test.content); isFrontMatterDelim != test.expected { t.Errorf("Expected %q isFrontMatterDelim = %t, got: %t", test.content, test.expected, isFrontMatterDelim) } } } } func TestExtractFrontMatter(t *testing.T) { tests := []struct { frontmatter string extracted []byte errIsNil bool }{ {"", nil, false}, {"-", nil, false}, {"---\n", nil, false}, {"---\nfoobar", nil, false}, {"---\nfoobar\nbarfoo\nfizbaz\n", nil, false}, {"---\nblar\n-\n", nil, false}, {"---\nralb\n---\n", []byte("---\nralb\n---\n"), true}, {"---\neof\n---", []byte("---\neof\n---"), true}, {"--- \neof\n---", []byte("---\neof\n---"), true}, {"---\nminc\n---\ncontent", []byte("---\nminc\n---\n"), true}, {"---\nminc\n--- \ncontent", []byte("---\nminc\n---\n"), true}, {"--- \nminc\n--- \ncontent", []byte("---\nminc\n---\n"), true}, {"---\ncnim\n---\ncontent\n", []byte("---\ncnim\n---\n"), true}, {"---\ntitle: slug doc 2\nslug: slug-doc-2\n---\ncontent\n", []byte("---\ntitle: slug doc 2\nslug: slug-doc-2\n---\n"), true}, {"---\npermalink: '/blog/title---subtitle.html'\n---\ncontent\n", []byte("---\npermalink: '/blog/title---subtitle.html'\n---\n"), true}, } for _, test := range tests { for _, ending := range lineEndings { test.frontmatter = strings.Replace(test.frontmatter, "\n", ending, -1) test.extracted = bytes.Replace(test.extracted, []byte("\n"), []byte(ending), -1) for _, delim := range delimiters { test.frontmatter = strings.Replace(test.frontmatter, "---", delim, -1) test.extracted = bytes.Replace(test.extracted, []byte("---"), []byte(delim), -1) line, err := peekLine(bufio.NewReader(strings.NewReader(test.frontmatter))) if err != nil { continue } l, r := determineDelims(line) fm, err := extractFrontMatterDelims(bufio.NewReader(strings.NewReader(test.frontmatter)), l, r) if (err == nil) != test.errIsNil { t.Logf("\n%q\n", string(test.frontmatter)) t.Errorf("Expected err == nil => %t, got: %t. err: %s", test.errIsNil, err == nil, err) continue } if !bytes.Equal(fm, test.extracted) { t.Errorf("Frontmatter did not match:\nexp: %q\ngot: %q", string(test.extracted), fm) } } } } } func TestExtractFrontMatterDelim(t *testing.T) { var ( noErrExpected = true errExpected = false ) tests := []struct { frontmatter string extracted string errIsNil bool }{ {"", "", errExpected}, {"{", "", errExpected}, {"{}", "{}", noErrExpected}, {"{} ", "{}", noErrExpected}, {"{ } ", "{ }", noErrExpected}, {"{ { }", "", errExpected}, {"{ { } }", "{ { } }", noErrExpected}, {"{ { } { } }", "{ { } { } }", noErrExpected}, {"{\n{\n}\n}\n", "{\n{\n}\n}", noErrExpected}, {"{\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}, {"{\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}, } for _, test := range tests { fm, err := extractFrontMatterDelims(bufio.NewReader(strings.NewReader(test.frontmatter)), []byte("{"), []byte("}")) if (err == nil) != test.errIsNil { t.Logf("\n%q\n", string(test.frontmatter)) t.Errorf("Expected err == nil => %t, got: %t. err: %s", test.errIsNil, err == nil, err) continue } if !bytes.Equal(fm, []byte(test.extracted)) { t.Logf("\n%q\n", string(test.frontmatter)) t.Errorf("Frontmatter did not match:\nexp: %q\ngot: %q", string(test.extracted), fm) } } }