package cnm import ( "reflect" "strings" "testing" ) var parseTests = map[string]*Document{ "": &Document{}, "foo\n\tbar\ntitle\n\ttest": &Document{ Title: "test", }, "foo\n\tbar\ntitle\n\ttest\nfoo\n\tbar": &Document{ Title: "test", }, "title\n\ttest": &Document{ Title: "test", }, "\ntitle\n\ttest\n": &Document{ Title: "test", }, "title\n\ttest\n\n": &Document{ Title: "test", }, "\ntitle\n\t\t\t\t\t\n\t\tfoo bar": &Document{ Title: "foo bar", }, "site\n\tfoo\nsite\n\t\tbar\n": &Document{ Site: Site{Children: []Site{ Site{Path: "foo"}, }}, }, "content\n\ttext\n\t\tfoo\ncontent\n\t\tbar\n": &Document{ Content: &ContentBlock{ name: "content", children: []Block{ &TextBlock{ Format: "", Contents: TextPlainContents{ Paragraphs: []string{"foo"}, }, }, }, }, }, "\n\ttitle\n\t\t\t\t\t\n\tfoo bar": &Document{}, "\ttitle\n\t\tfoo\n": &Document{}, "\tsite\n\t\tfoo\n": &Document{}, "\tlinks\n\t\tfoo\n": &Document{}, "links\n\tfoo": &Document{ Links: []Link{ Link{ URL: "foo", }, }, }, "qwe\ntitle\n\tasd": &Document{ Title: "asd", }, "links\n\t qwe\n\tasd": &Document{ Links: []Link{ Link{URL: "asd"}, }, }, "site\n\t qwe\n\tasd": &Document{ Site: Site{Children: []Site{ Site{Path: "asd"}, }}, }, "site\n\tba\\nr": &Document{ Site: Site{ Children: []Site{ Site{ Path: "ba\nr", }, }, }, }, "site\n\t\t\t\tba\\nr": &Document{}, "site\n\tfoo\tbar": &Document{ Site: Site{ Children: []Site{ Site{ Path: "foo", Name: "bar", }, }, }, }, "\t\tsite\n\t\t\t\tfoo": &Document{}, "\tsite\n\tbar": &Document{}, "content\n\tsection test\n": &Document{ Content: &ContentBlock{ name: "content", args: nil, children: []Block{ &SectionBlock{ContentBlock{ name: "section", args: []string{"test"}, }}, }, }, }, "content\n\ttext __nonexistent__\n\t\tqwe zxc\n\n\t\tasd\\n123": &Document{ Content: &ContentBlock{ name: "content", args: nil, children: []Block{ &TextBlock{ Format: "__nonexistent__", Contents: TextRawContents{"qwe zxc\n\nasd\\n123"}, }, }, }, }, "content\n\tnosuchblock\n\tsection test\n": &Document{ Content: &ContentBlock{ name: "content", args: nil, children: []Block{ &SectionBlock{ContentBlock{ name: "section", args: []string{"test"}, }}, }, }, }, "content\n\tnosuchblock\n\tsection test\n\n\tnosuchblock2": &Document{ Content: &ContentBlock{ name: "content", args: nil, children: []Block{ &SectionBlock{ContentBlock{ name: "section", args: []string{"test"}, }}, }, }, }, "content\n\tsection\n\t\tnosuchblock\n\t\tsection\n\t\t\ttext\n\t\t\t\ttest": &Document{ Content: &ContentBlock{ name: "content", args: nil, children: []Block{ &SectionBlock{ContentBlock{ name: "section", args: nil, children: []Block{ &SectionBlock{ContentBlock{ name: "section", args: nil, children: []Block{ &TextBlock{ Format: "", Contents: TextPlainContents{Paragraphs: []string{ "test", }}, }, }, }}, }, }}, }, }, }, "content\n\ttable\n\t\tnosuchblock\n\t\trow\n\t\t\ttext\n\t\t\t\ttest": &Document{ Content: &ContentBlock{ name: "content", args: nil, children: []Block{ &TableBlock{ContentBlock{ name: "table", args: nil, children: []Block{ &RowBlock{ContentBlock{ name: "row", args: nil, children: []Block{ &TextBlock{ Format: "", Contents: TextPlainContents{Paragraphs: []string{ "test", }}, }, }, }}, }, }}, }, }, }, "site\n\t\ttest\n\tfoo\\ bar baz\n" + "links\n\tfoo\\ bar baz\n\t\t\tquux\n" + "content\n" + "links\n\t\ttest\n" + "content\n" + "\tsection\n\tsection qweasd\n" + "\tsection foo\\ bar baz\n\t\ttext\n\t\t\ttest\n": &Document{ Site: Site{ Children: []Site{ Site{ Path: "foo bar", Name: "baz", }, }, }, Links: []Link{ Link{ URL: "foo bar", Name: "baz", Description: "quux", }, }, Content: &ContentBlock{ name: "content", args: nil, children: []Block{ &SectionBlock{ContentBlock{ name: "section", args: nil, }}, &SectionBlock{ContentBlock{ name: "section", args: []string{"qweasd"}, }}, &SectionBlock{ContentBlock{ name: "section", args: []string{"foo bar baz"}, children: []Block{ &TextBlock{ Contents: TextPlainContents{[]string{ "test", }}, }, }, }}, }, }, }, "title\n\tfoo bar\n" + "links\n\tqwe asd\n\tzxc 123\n" + "site\n\tfoo Foo\n\t\tbar\n\t\tbaz/qu\\ ux Test\n\t\t\t123\n" + "title\n\tbaz\n" + "links\n\tfoo\n" + "site\n\ttest": &Document{ Title: "foo bar baz", Links: []Link{ Link{"qwe", "asd", ""}, Link{"zxc", "123", ""}, Link{"foo", "", ""}, }, Site: Site{ Children: []Site{ Site{ Path: "foo", Name: "Foo", Children: []Site{ Site{Path: "bar"}, Site{ Path: "baz/qu ux", Name: "Test", Children: []Site{ Site{Path: "123"}, }, }, }, }, Site{Path: "test"}, }, }, }, ` thing stuff whatever title blah Test title document content section Test section text This is \n just a text pre t e \n s t preformatted text raw text/plain of various \n features section of\ the table header text Column 1 text Column 2 row text CNM text document format row section lorem list text ipsum list ordered list unordered text dolor sit amet embed text/cnm cnp://example.com/ stuff thing whatever `: &Document{ Title: "Test document", Content: &ContentBlock{ name: "content", args: nil, children: []Block{ &SectionBlock{ContentBlock{ name: "section", args: []string{"Test section"}, children: []Block{ &TextBlock{ Contents: TextPlainContents{[]string{ "This is \n just a", }}, }, &TextBlock{ Format: "pre", Contents: TextPreContents{ " t e \n s t \n\t\n\n\npreformatted text", }, }, &RawBlock{ Syntax: "text/plain", Contents: TextRawContents{"of various \\n features"}, }, &SectionBlock{ContentBlock{ name: "section", args: []string{"of the"}, children: []Block{ &TableBlock{ContentBlock{ name: "table", args: nil, children: []Block{ &HeaderBlock{ContentBlock{ name: "header", args: nil, children: []Block{ &TextBlock{ Contents: TextPlainContents{[]string{ "Column 1", }}, }, &TextBlock{ Contents: TextPlainContents{[]string{ "Column 2", }}, }, }, }}, &RowBlock{ContentBlock{ name: "row", args: nil, children: []Block{ &TextBlock{ Contents: TextPlainContents{[]string{ "CNM", }}, }, &TextBlock{ Contents: TextPlainContents{[]string{ "document", "format", }}, }, }, }}, &RowBlock{ContentBlock{ name: "row", args: nil, children: []Block{ &SectionBlock{ContentBlock{ name: "section", args: nil, }}, &ListBlock{ContentBlock{ name: "list", args: nil, children: []Block{ &TextBlock{ Contents: TextPlainContents{[]string{ "ipsum", }}, }, &ListBlock{ContentBlock{ name: "list", args: []string{"ordered"}, children: []Block{ &ListBlock{ContentBlock{ name: "list", args: []string{"unordered"}, children: []Block{ &TextBlock{ Contents: TextPlainContents{[]string{ "dolor", "sit amet", }}, }, }, }}, }, }}, }, }}, }, }}, }, }}, }, }}, }, }}, &EmbedBlock{ Type: "text/cnm", URL: "cnp://example.com/", Description: "thing whatever", }, }, }, }, } func TestParse(t *testing.T) { for k, v := range parseTests { t.Run(k, func(t *testing.T) { d, err := ParseDocument(strings.NewReader(k)) if err != nil { t.Fatalf("ParseDocument(%q): error: %v", k, err) } if !documentEqual(t, d, v) { t.Fatalf("ParseDocument(%q):\nexpected:\n%#v\n got:\n%#v", k, v, d) } }) } } func documentEqual(t *testing.T, a, b *Document) bool { if a.Title != b.Title { t.Log("titles differ") return false } if len(a.Links) != len(b.Links) { t.Log("links count differ") return false } for i := range a.Links { if !linkEqual(t, a.Links[i], b.Links[i]) { return false } } if !siteEqual(t, a.Site, b.Site) { return false } if !contentBlockEqual(t, a.Content, b.Content) { return false } return true } func linkEqual(t *testing.T, a, b Link) bool { if a != b { t.Log("link differs") return false } return true } func siteEqual(t *testing.T, a, b Site) bool { if a.Path != b.Path { t.Log("site path differs") return false } if a.Name != b.Name { t.Log("site name differs") return false } if len(a.Children) != len(b.Children) { t.Log("site children count differs") return false } for i := range a.Children { if !siteEqual(t, a.Children[i], b.Children[i]) { return false } } return true } func blockEqual(t *testing.T, a, b Block) bool { switch va := a.(type) { case *Document: vb, ok := b.(*Document) if !ok { t.Log("type mismatch") return false } return documentEqual(t, va, vb) case *SectionBlock: vb, ok := b.(*SectionBlock) if !ok { t.Log("type mismatch") return false } return sectionBlockEqual(t, va, vb) case *TextBlock: vb, ok := b.(*TextBlock) if !ok { t.Log("type mismatch") return false } return textBlockEqual(t, va, vb) case *RawBlock: vb, ok := b.(*RawBlock) if !ok { t.Log("type mismatch") return false } return rawBlockEqual(t, va, vb) case *ListBlock: vb, ok := b.(*ListBlock) if !ok { t.Log("type mismatch") return false } return listBlockEqual(t, va, vb) case *TableBlock: vb, ok := b.(*TableBlock) if !ok { t.Log("type mismatch") return false } return tableBlockEqual(t, va, vb) case *HeaderBlock: vb, ok := b.(*HeaderBlock) if !ok { t.Log("type mismatch") return false } return headerBlockEqual(t, va, vb) case *RowBlock: vb, ok := b.(*RowBlock) if !ok { t.Log("type mismatch") return false } return rowBlockEqual(t, va, vb) case *EmbedBlock: vb, ok := b.(*EmbedBlock) if !ok { t.Log("type mismatch") return false } return embedBlockEqual(t, va, vb) case *ContentBlock: vb, ok := b.(*ContentBlock) if !ok { t.Logf("type mismatch: %T, %T", a, b) return false } return contentBlockEqual(t, va, vb) case nil: return b == nil default: // shouldn't happen t.Logf("unknown type: %T", a) return false } } func contentBlockEqual(t *testing.T, a, b *ContentBlock) bool { if (a == nil) != (b == nil) { t.Log("content blocks are not both nil or non-nil") return false } if a == nil { return true } if a.Name() != b.Name() { t.Log("content block names differ") return false } if !argsEqual(t, a.Args(), b.Args()) { return false } ca, cb := a.Children(), b.Children() if len(ca) != len(cb) { t.Log("content block children count differs") return false } for i := range ca { if !blockEqual(t, ca[i], cb[i]) { return false } } return true } func argsEqual(t *testing.T, a, b []string) bool { if len(a) != len(b) { t.Log("args count differs") return false } for i := range a { if a[i] != b[i] { t.Log("arg differs") return false } } return true } func sectionBlockEqual(t *testing.T, a, b *SectionBlock) bool { if a.Title() != b.Title() { t.Log("section titles differ") return false } if !contentBlockEqual(t, &a.ContentBlock, &b.ContentBlock) { return false } return true } func textBlockEqual(t *testing.T, a, b *TextBlock) bool { if !argsEqual(t, a.Args(), b.Args()) { return false } if !textContentsEqual(t, a.Contents, b.Contents) { return false } return true } func textContentsEqual(t *testing.T, a, b TextContents) bool { switch va := a.(type) { case TextPlainContents: vb, ok := b.(TextPlainContents) if !ok { t.Log("text content type differs") return false } return textPlainContentsEqual(t, va, vb) case TextPreContents: vb, ok := b.(TextPreContents) if !ok { t.Log("text content type differs") return false } return textPreContentsEqual(t, va, vb) case TextRawContents: vb, ok := b.(TextRawContents) if !ok { t.Log("text content type differs") return false } return textRawContentsEqual(t, va, vb) default: // handle unknown contents, e.g. text fmt if reflect.TypeOf(a) != reflect.TypeOf(b) { t.Log("unknown text content format type differs") return false } if !reflect.DeepEqual(a, b) { t.Log("unknown text content format contents differ") return false } return true } } func textPlainContentsEqual(t *testing.T, a, b TextPlainContents) bool { if len(a.Paragraphs) != len(b.Paragraphs) { t.Log("text plain paragraph count differs") return false } for i := range a.Paragraphs { if a.Paragraphs[i] != b.Paragraphs[i] { t.Log("text plain paragraph differs") return false } } return true } func textPreContentsEqual(t *testing.T, a, b TextPreContents) bool { if a.Text != b.Text { t.Log("text pre contents differ") return false } return true } func textRawContentsEqual(t *testing.T, a, b TextRawContents) bool { if a.Text != b.Text { t.Log("text raw contents differ") return false } return true } func rawBlockEqual(t *testing.T, a, b *RawBlock) bool { return argsEqual(t, a.Args(), b.Args()) && textRawContentsEqual(t, a.Contents, b.Contents) } func listBlockEqual(t *testing.T, a, b *ListBlock) bool { if a.Ordered() != b.Ordered() { t.Log("list block mode differs") } return contentBlockEqual(t, &a.ContentBlock, &b.ContentBlock) } func tableBlockEqual(t *testing.T, a, b *TableBlock) bool { return contentBlockEqual(t, &a.ContentBlock, &b.ContentBlock) } func rowBlockEqual(t *testing.T, a, b *RowBlock) bool { return contentBlockEqual(t, &a.ContentBlock, &b.ContentBlock) } func headerBlockEqual(t *testing.T, a, b *HeaderBlock) bool { return contentBlockEqual(t, &a.ContentBlock, &b.ContentBlock) } func embedBlockEqual(t *testing.T, a, b *EmbedBlock) bool { return argsEqual(t, a.Args(), b.Args()) }