Skip to content

Commit 1585ea8

Browse files
author
merge-queue-bot
committed
Merge PR #692: test(lsp/rename): add dedicated unit tests for 13 unexported helpers
2 parents 6cc286a + ad2864f commit 1585ea8

3 files changed

Lines changed: 260 additions & 17 deletions

File tree

PLAN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ footer: |
225225
| 2606231013 || sonnet | [Add dedicated unit tests for inline_scan.go helpers](plan/2606231013_arch-fix-inline-scan-helper-tests.md) |
226226
| 2606231014 || sonnet | [Add dedicated unit tests for samefileanchor helper functions](plan/2606231014_arch-fix-samefileanchor-helper-tests.md) |
227227
| 2606240211 || sonnet | [Add dedicated unit tests for locate.go helpers](plan/2606240211_arch-fix-locate-helper-tests.md) |
228-
| 2606240212 | 🔲 | sonnet | [Add dedicated unit tests for lsp/rename.go helpers](plan/2606240212_arch-fix-lsp-rename-helper-tests.md) |
228+
| 2606240212 | | sonnet | [Add dedicated unit tests for lsp/rename.go helpers](plan/2606240212_arch-fix-lsp-rename-helper-tests.md) |
229229
| 2606240213 | 🔲 | sonnet | [Add dedicated unit tests for export.go helpers and two small rename helpers](plan/2606240213_arch-fix-export-helper-tests.md) |
230230
| 2606240214 || sonnet | [Remove duplicated helpers between lsp/rename.go and rename/rename.go](plan/2606240214_arch-fix-rename-dedup.md) |
231231
<?/catalog?>

internal/lsp/rename_test.go

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,3 +528,240 @@ func TestRefUseLabelBytesCollapsedTrailingEmptyBrackets(t *testing.T) {
528528
require.True(t, ok, "expected match for cursor inside trailing []")
529529
assert.Equal(t, "Docs API", string(row[start:end]))
530530
}
531+
532+
// TestIsValidRefDefLine drives isValidRefDefLine with a real ref-def
533+
// line (returns true), a heading line (returns false), and a blank
534+
// line (returns false).
535+
func TestIsValidRefDefLine(t *testing.T) {
536+
t.Parallel()
537+
src := []byte("# T\n\n[docs]: https://example.com\n")
538+
assert.True(t, isValidRefDefLine(src, 3))
539+
assert.False(t, isValidRefDefLine(src, 1))
540+
assert.False(t, isValidRefDefLine(src, 2))
541+
}
542+
543+
// TestHeadingPrepareRangeATX drives headingPrepareRange with a simple
544+
// ATX heading and verifies the range covers the text only.
545+
func TestHeadingPrepareRangeATX(t *testing.T) {
546+
t.Parallel()
547+
src := []byte("# Hello\n")
548+
res, ok := headingPrepareRange(src, 1, "Hello")
549+
require.True(t, ok)
550+
assert.Equal(t, "Hello", res.Placeholder)
551+
assert.Equal(t, 0, res.Range.Start.Line)
552+
assert.Equal(t, 2, res.Range.Start.Character)
553+
assert.Equal(t, 7, res.Range.End.Character)
554+
}
555+
556+
// TestAtxHeadingTextStart drives atxHeadingTextStart directly with
557+
// ATX headings, a no-space header, a too-deep header, and plain text.
558+
func TestAtxHeadingTextStart(t *testing.T) {
559+
t.Parallel()
560+
cases := []struct {
561+
row string
562+
wantI int
563+
wantOK bool
564+
}{
565+
{"# Hello", 2, true},
566+
{"## Hi", 3, true},
567+
{"###### Six", 7, true},
568+
{" ## Indented", 6, true},
569+
{"##\tTab", 3, true},
570+
{"#NoSpace", 0, false},
571+
{"####### TooMany", 0, false},
572+
{"plain text", 0, false},
573+
{"", 0, false},
574+
}
575+
for _, tc := range cases {
576+
i, ok := atxHeadingTextStart([]byte(tc.row))
577+
assert.Equal(t, tc.wantOK, ok, "row=%q", tc.row)
578+
assert.Equal(t, tc.wantI, i, "row=%q", tc.row)
579+
}
580+
}
581+
582+
// TestTrimTrailingHashRun drives trimTrailingHashRun with a closing
583+
// hash run preceded by space (stripped), a hash run without space
584+
// (kept), and a row with no trailing hash (unchanged).
585+
func TestTrimTrailingHashRun(t *testing.T) {
586+
t.Parallel()
587+
cases := []struct {
588+
row string
589+
start, end int
590+
want int
591+
}{
592+
{"## Setup ###", 3, 12, 8}, // trailing " ###" stripped; text ends at 8
593+
{"## Setup ##", 3, 11, 8}, // trailing " ##" stripped
594+
{"## Setup #", 3, 10, 8}, // trailing " #" stripped
595+
{"## Setup#", 3, 9, 9}, // no preceding space — kept
596+
{"## Setup", 3, 8, 8}, // no trailing hash — unchanged
597+
{"## Setup ", 3, 11, 11}, // trailing spaces only, no hash — unchanged
598+
}
599+
for _, tc := range cases {
600+
got := trimTrailingHashRun([]byte(tc.row), tc.start, tc.end)
601+
assert.Equal(t, tc.want, got, "row=%q", tc.row)
602+
}
603+
}
604+
605+
// TestSkipLeadingSpaces drives skipLeadingSpaces with no leading
606+
// spaces, fewer than max spaces, and more than max spaces.
607+
func TestSkipLeadingSpaces(t *testing.T) {
608+
t.Parallel()
609+
assert.Equal(t, 0, skipLeadingSpaces([]byte("abc"), 3))
610+
assert.Equal(t, 2, skipLeadingSpaces([]byte(" abc"), 3))
611+
assert.Equal(t, 3, skipLeadingSpaces([]byte(" abc"), 3))
612+
assert.Equal(t, 3, skipLeadingSpaces([]byte(" abc"), 3))
613+
assert.Equal(t, 0, skipLeadingSpaces([]byte(""), 3))
614+
}
615+
616+
// TestTrimRightSpace drives trimRightSpace with trailing spaces,
617+
// trailing tab, no trailing whitespace, and all-whitespace input.
618+
func TestTrimRightSpace(t *testing.T) {
619+
t.Parallel()
620+
assert.Equal(t, 5, trimRightSpace([]byte("hello "), 0, 7))
621+
assert.Equal(t, 5, trimRightSpace([]byte("hello\t "), 0, 7))
622+
assert.Equal(t, 5, trimRightSpace([]byte("hello"), 0, 5))
623+
assert.Equal(t, 0, trimRightSpace([]byte(" "), 0, 3))
624+
}
625+
626+
// TestTrimmedRange drives trimmedRange with leading-and-trailing
627+
// whitespace, no whitespace, and an all-whitespace row.
628+
func TestTrimmedRange(t *testing.T) {
629+
t.Parallel()
630+
start, end := trimmedRange([]byte(" hello "))
631+
assert.Equal(t, 2, start)
632+
assert.Equal(t, 7, end)
633+
start, end = trimmedRange([]byte("nospace"))
634+
assert.Equal(t, 0, start)
635+
assert.Equal(t, 7, end)
636+
start, end = trimmedRange([]byte(" "))
637+
assert.Equal(t, 3, start)
638+
assert.Equal(t, 3, end)
639+
}
640+
641+
// TestRefDefPrepareRangeHappy drives refDefPrepareRange with a
642+
// well-formed `[label]: url` line and verifies the returned
643+
// placeholder and range.
644+
func TestRefDefPrepareRangeHappy(t *testing.T) {
645+
t.Parallel()
646+
src := []byte("[docs]: https://example.com\n")
647+
res, ok := refDefPrepareRange(src, 1, "")
648+
require.True(t, ok)
649+
assert.Equal(t, "docs", res.Placeholder)
650+
assert.Equal(t, 0, res.Range.Start.Line)
651+
assert.Equal(t, 1, res.Range.Start.Character)
652+
assert.Equal(t, 5, res.Range.End.Character)
653+
}
654+
655+
// TestRefUsePrepareRangeHappy drives refUsePrepareRange with the
656+
// cursor inside the trailing `[label]` of a full reference link.
657+
func TestRefUsePrepareRangeHappy(t *testing.T) {
658+
t.Parallel()
659+
src := []byte("See [text][docs] here.\n")
660+
res, ok := refUsePrepareRange(src, 1, 12, "docs")
661+
require.True(t, ok)
662+
assert.Equal(t, "docs", res.Placeholder)
663+
assert.Equal(t, 0, res.Range.Start.Line)
664+
assert.Equal(t, 11, res.Range.Start.Character)
665+
assert.Equal(t, 15, res.Range.End.Character)
666+
}
667+
668+
// TestRefUseLabelBytesAllForms drives refUseLabelBytes across the
669+
// four reference forms (full cursor-in-text, full cursor-in-label,
670+
// shortcut, collapsed) and the no-match case.
671+
func TestRefUseLabelBytesAllForms(t *testing.T) {
672+
t.Parallel()
673+
cases := []struct {
674+
row string
675+
cursor int
676+
label string
677+
wantOK bool
678+
wantLabel string
679+
}{
680+
{`[text][docs]`, 2, "docs", true, "docs"}, // full: cursor in text
681+
{`[text][docs]`, 5, "docs", true, "docs"}, // full: cursor at ] of text (pr.close boundary)
682+
{`[text][docs]`, 6, "docs", true, "docs"}, // full: cursor at [ of label (pr.open boundary)
683+
{`[text][docs]`, 8, "docs", true, "docs"}, // full: cursor in label
684+
{`[docs]`, 2, "docs", true, "docs"}, // shortcut
685+
{`[docs][]`, 2, "docs", true, "docs"}, // collapsed: cursor in leading
686+
{`[docs][]`, 7, "docs", true, "docs"}, // collapsed: cursor in trailing
687+
{`[other]`, 2, "docs", false, ""}, // wrong label
688+
{`[docs]`, 99, "docs", false, ""}, // cursor outside
689+
}
690+
for _, tc := range cases {
691+
row := []byte(tc.row)
692+
start, end, ok := refUseLabelBytes(row, tc.cursor, tc.label)
693+
assert.Equal(t, tc.wantOK, ok, "row=%q cursor=%d", tc.row, tc.cursor)
694+
if ok {
695+
assert.Equal(t, tc.wantLabel, string(row[start:end]), "row=%q cursor=%d", tc.row, tc.cursor)
696+
} else {
697+
assert.Equal(t, 0, start, "row=%q cursor=%d", tc.row, tc.cursor)
698+
assert.Equal(t, 0, end, "row=%q cursor=%d", tc.row, tc.cursor)
699+
}
700+
}
701+
}
702+
703+
// TestMatchLeadingPairHappy drives matchLeadingPair for the full
704+
// `[text][label]` case (returns trailing label) and the collapsed
705+
// `[label][]` case (returns leading pair content).
706+
func TestMatchLeadingPairHappy(t *testing.T) {
707+
t.Parallel()
708+
// full: [text][label] — leading pair, returns trailing label
709+
row := []byte(`[text][label]`)
710+
pairs := bracketPairs(row)
711+
require.Len(t, pairs, 2)
712+
start, end, ok := matchLeadingPair(row, pairs, 0, "label")
713+
require.True(t, ok)
714+
assert.Equal(t, "label", string(row[start:end]))
715+
716+
// collapsed: [label][] — leading pair, returns leading content
717+
row2 := []byte(`[label][]`)
718+
pairs2 := bracketPairs(row2)
719+
require.Len(t, pairs2, 2)
720+
start2, end2, ok2 := matchLeadingPair(row2, pairs2, 0, "label")
721+
require.True(t, ok2)
722+
assert.Equal(t, "label", string(row2[start2:end2]))
723+
}
724+
725+
// TestMatchTrailingPairHappy drives matchTrailingPair for the full
726+
// `[text][label]` case (returns trailing label) and the collapsed
727+
// `[label][]` case (returns leading pair content).
728+
func TestMatchTrailingPairHappy(t *testing.T) {
729+
t.Parallel()
730+
// full: [text][label] — trailing pair, returns label
731+
row := []byte(`[text][label]`)
732+
pairs := bracketPairs(row)
733+
require.Len(t, pairs, 2)
734+
start, end, ok := matchTrailingPair(row, pairs, 1, "label")
735+
require.True(t, ok)
736+
assert.Equal(t, "label", string(row[start:end]))
737+
738+
// collapsed: [label][] — trailing empty pair, returns leading content
739+
row2 := []byte(`[label][]`)
740+
pairs2 := bracketPairs(row2)
741+
require.Len(t, pairs2, 2)
742+
start2, end2, ok2 := matchTrailingPair(row2, pairs2, 1, "label")
743+
require.True(t, ok2)
744+
assert.Equal(t, "label", string(row2[start2:end2]))
745+
}
746+
747+
// TestBracketPairsBasic drives bracketPairs with a single pair, two
748+
// adjacent pairs, and an empty row.
749+
func TestBracketPairsBasic(t *testing.T) {
750+
t.Parallel()
751+
// single pair
752+
pairs := bracketPairs([]byte(`[a]`))
753+
require.Len(t, pairs, 1)
754+
assert.Equal(t, 0, pairs[0].open)
755+
assert.Equal(t, 2, pairs[0].close)
756+
757+
// two adjacent pairs
758+
row := []byte(`[a][b]`)
759+
pairs = bracketPairs(row)
760+
require.Len(t, pairs, 2)
761+
assert.Equal(t, "a", string(row[pairs[0].open+1:pairs[0].close]))
762+
assert.Equal(t, "b", string(row[pairs[1].open+1:pairs[1].close]))
763+
764+
// empty row
765+
pairs = bracketPairs([]byte(``))
766+
assert.Empty(t, pairs)
767+
}

plan/2606240212_arch-fix-lsp-rename-helper-tests.md

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
id: 2606240212
33
title: Add dedicated unit tests for lsp/rename.go helpers
4-
status: "🔲"
4+
status: ""
55
model: sonnet
66
summary: >-
77
internal/lsp/rename.go has 13 unexported
@@ -55,18 +55,24 @@ The 13 helpers below need tests:
5555

5656
## Acceptance Criteria
5757

58-
- [ ] `rename_test.go` contains
59-
`TestisValidRefDefLine`,
60-
`TestheadingPrepareRange`,
61-
`TestatxHeadingTextStart`,
62-
`TesttrimTrailingHashRun`,
63-
`TestskipLeadingSpaces`,
64-
`TesttrimRightSpace`, `TesttrimmedRange`,
65-
`TestrefDefPrepareRange`,
66-
`TestrefUsePrepareRange`,
67-
`TestrefUseLabelBytes`,
68-
`TestmatchLeadingPair`,
69-
`TestmatchTrailingPair`,
70-
`TestbracketPairs`.
71-
- [ ] `go test ./internal/lsp/...` is green.
72-
- [ ] `mdsmith check .` is green.
58+
Go 1.25 enforces that test names start with an uppercase
59+
letter after `Test`. The originally planned lowercase
60+
names (`TestisValidRefDefLine`, etc.) do not compile.
61+
Each test is prefixed with a capital letter or a
62+
disambiguating suffix (e.g. `Happy`, `ATX`, `Basic`).
63+
64+
- [x] `rename_test.go` contains a dedicated test for
65+
each helper: `TestIsValidRefDefLine`,
66+
`TestHeadingPrepareRangeATX`,
67+
`TestAtxHeadingTextStart`,
68+
`TestTrimTrailingHashRun`,
69+
`TestSkipLeadingSpaces`,
70+
`TestTrimRightSpace`, `TestTrimmedRange`,
71+
`TestRefDefPrepareRangeHappy`,
72+
`TestRefUsePrepareRangeHappy`,
73+
`TestRefUseLabelBytesAllForms`,
74+
`TestMatchLeadingPairHappy`,
75+
`TestMatchTrailingPairHappy`,
76+
`TestBracketPairsBasic`.
77+
- [x] `go test ./internal/lsp/...` is green.
78+
- [x] `mdsmith check .` is green.

0 commit comments

Comments
 (0)