~bigbes/sr-ht-spec

ref: d1621dca89d49b047348971fb9a682b232e19a26 sr-ht-spec/prosediff/segment_test.go -rw-r--r-- 6.9 KiB
d1621dca — Eugene Blikh fix(prosediff): an equal run's separator comes from whichever side has one (spec-by6.5) 13 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
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)
	}
}

// A thematic break carries no text and therefore no text segment, so its
// position has to be recovered from the source rather than read off the AST.
// Every rule used to report line 1 — including two rules in one document, which
// is the one thing a line-numbered renderer must never be told.
func TestThematicBreakReportsItsOwnLine(t *testing.T) {
	src := "# One\n\npara\n\n---\n\nmore\n\n---\n\n> quoted\n>\n> ---\n\ntail\n"
	var rules []Block
	for _, b := range Segment([]byte(src)) {
		if b.Kind == KindThematicBreak {
			rules = append(rules, b)
		}
	}
	require.Len(t, rules, 3)
	assert.Equal(t, []int{5, 9, 13}, []int{rules[0].StartLine, rules[1].StartLine, rules[2].StartLine})
	for _, r := range rules {
		assert.Equal(t, r.StartLine, r.EndLine, "a rule occupies one line")
	}
}

// Consecutive rules have nothing but their order to tell them apart, and are
// the case a naive "scan forward for the next rule" gets wrong by reporting the
// first one twice.
func TestAdjacentThematicBreaksDoNotShareALine(t *testing.T) {
	var lines []int
	for _, b := range Segment([]byte("a\n\n---\n\n---\n\n---\n\nb\n")) {
		if b.Kind == KindThematicBreak {
			lines = append(lines, b.StartLine)
		}
	}
	assert.Equal(t, []int{3, 5, 7}, lines)
}

// Line numbers are metadata, not identity: a block's hash covers its structure
// and its content and nothing about where it sits. Moving a rule down the
// document must therefore leave every anchor pointing where it did, which is
// what makes fixing the line number above safe for stored comments.
func TestThematicBreakHashIgnoresItsPosition(t *testing.T) {
	near := Segment([]byte("a\n\n---\n"))
	far := Segment([]byte("a\n\nb\n\nc\n\n---\n"))
	require.Equal(t, KindThematicBreak, near[len(near)-1].Kind)
	require.Equal(t, KindThematicBreak, far[len(far)-1].Kind)
	assert.Equal(t, near[len(near)-1].Hash, far[len(far)-1].Hash)
	assert.NotEqual(t, near[len(near)-1].StartLine, far[len(far)-1].StartLine)
}

// A document that is nothing but "---" was admitted as frontmatter and then
// sliced at [4:] on three bytes. The review page renders whatever is on a
// branch, so this was a panic reachable from any one-line file.
func TestBareRuleDocumentIsNotFrontmatter(t *testing.T) {
	got := Segment([]byte("---"))
	require.Len(t, got, 1)
	assert.Equal(t, KindThematicBreak, got[0].Kind)
	assert.Equal(t, 1, got[0].StartLine)

	// The neighbouring shapes that must keep working.
	assert.NotPanics(t, func() { Segment([]byte("---\n")) })
	assert.NotPanics(t, func() { Segment([]byte("--")) })
	assert.NotPanics(t, func() { Segment([]byte("----")) })
	fm := Segment([]byte("---\nid: X\n---\n\nbody\n"))
	require.Len(t, fm, 2)
	assert.Equal(t, KindFrontmatter, fm[0].Kind)
}