package prosediff
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type wantBlock struct {
kind BlockKind
level int
quote int
text string
path []string
}
func TestSegment(t *testing.T) {
tests := []struct {
name string
src string
want []wantBlock
}{
{
name: "headings and paragraphs",
src: "# Title\n\nFirst para\nwrapped over lines.\n\n## Sub\n\nSecond para.\n",
want: []wantBlock{
{kind: KindHeading, level: 1, text: "Title"},
{kind: KindParagraph, text: "First para\nwrapped over lines.", path: []string{"Title"}},
{kind: KindHeading, level: 2, text: "Sub", path: []string{"Title"}},
{kind: KindParagraph, text: "Second para.", path: []string{"Title", "Sub"}},
},
},
{
name: "list items are separate blocks",
src: "- alpha\n- beta\n - nested\n",
want: []wantBlock{
{kind: KindListItem, level: 1, text: "alpha"},
{kind: KindListItem, level: 1, text: "beta"},
{kind: KindListItem, level: 2, text: "nested"},
},
},
{
name: "fenced code is one block",
src: "text\n\n```go\na := 1\nb := 2\n```\n",
want: []wantBlock{
{kind: KindParagraph, text: "text"},
{kind: KindCode, text: "a := 1\nb := 2"},
},
},
{
name: "table rows are separate blocks",
src: "| a | b |\n|---|---|\n| 1 | 2 |\n| 3 | 4 |\n",
want: []wantBlock{
{kind: KindTableHeader, text: "| a | b |"},
{kind: KindTableRow, text: "| 1 | 2 |"},
{kind: KindTableRow, text: "| 3 | 4 |"},
},
},
{
name: "block quote carries depth",
src: "> quoted text\n> continues\n",
want: []wantBlock{
{kind: KindParagraph, quote: 1, text: "quoted text\ncontinues"},
},
},
{
name: "frontmatter is its own block",
src: "---\nid: SPEC-0007\ntitle: x\n---\n\nBody.\n",
want: []wantBlock{
{kind: KindFrontmatter, text: "---\nid: SPEC-0007\ntitle: x\n---"},
{kind: KindParagraph, text: "Body."},
},
},
{
name: "empty document",
src: "",
want: nil,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := Segment([]byte(tc.src))
require.Len(t, got, len(tc.want))
for i, w := range tc.want {
b := got[i]
assert.Equal(t, w.kind, b.Kind, "block %d kind", i)
assert.Equal(t, w.level, b.Level, "block %d level", i)
assert.Equal(t, w.quote, b.QuoteDepth, "block %d quote depth", i)
assert.Equal(t, w.text, b.Text, "block %d text", i)
assert.Equal(t, w.path, b.HeadingPath, "block %d heading path", i)
assert.Equal(t, i, b.Ordinal)
assert.NotEmpty(t, b.Hash)
}
})
}
}
func TestSegmentLineNumbers(t *testing.T) {
src := "# Title\n\npara one\n\n```\ncode\n```\n"
got := Segment([]byte(src))
require.Len(t, got, 3)
assert.Equal(t, 1, got[0].StartLine)
assert.Equal(t, 3, got[1].StartLine)
assert.Equal(t, 6, got[2].StartLine)
}
func TestSegmentFrontmatterLineOffset(t *testing.T) {
src := "---\nid: X\n---\n\n# Title\n"
got := Segment([]byte(src))
require.Len(t, got, 2)
assert.Equal(t, KindFrontmatter, got[0].Kind)
assert.Equal(t, 5, got[1].StartLine)
}
// TestHashIgnoresWrappingForProse and its code counterpart are the two halves
// of the design's "code fences diff as code, prose diffs as prose" rule.
func TestHashIgnoresWrappingForProse(t *testing.T) {
a := Segment([]byte("one two three four\nfive six\n"))
b := Segment([]byte("one two\nthree four five six\n"))
require.Len(t, a, 1)
require.Len(t, b, 1)
assert.Equal(t, a[0].Hash, b[0].Hash)
}
func TestHashRespectsWhitespaceForCode(t *testing.T) {
a := Segment([]byte("```\nif x:\n y()\n```\n"))
b := Segment([]byte("```\nif x:\n\ty()\n```\n"))
require.Len(t, a, 1)
require.Len(t, b, 1)
assert.NotEqual(t, a[0].Hash, b[0].Hash)
}
func TestHashRespectsStructure(t *testing.T) {
para := Segment([]byte("same words here\n"))
item := Segment([]byte("- same words here\n"))
require.Len(t, para, 1)
require.Len(t, item, 1)
assert.NotEqual(t, para[0].Hash, item[0].Hash)
}
func TestKindProse(t *testing.T) {
for _, k := range []BlockKind{KindHeading, KindParagraph, KindListItem, KindTableRow, KindTableHeader, KindThematicBreak} {
assert.True(t, k.Prose(), "%s should diff as prose", k)
}
for _, k := range []BlockKind{KindCode, KindFrontmatter, KindHTML} {
assert.False(t, k.Prose(), "%s should diff as code", k)
}
}