all repos

clerk @ 1efa5a1

missing tooling for ledger/hledger

clerk/internal/lsp/textdocument_rename_test.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
lsp: rename, 1 month ago
1
package lsp
2
3
import (
4
	"fmt"
5
	"os"
6
	"path/filepath"
7
	"sort"
8
	"strings"
9
	"testing"
10
11
	"go.lsp.dev/protocol"
12
	"go.lsp.dev/uri"
13
14
	"olexsmir.xyz/clerk/internal/lsp/lsputil"
15
	"olexsmir.xyz/clerk/internal/testutil/golden"
16
)
17
18
func TestValidateRenameName(t *testing.T) {
19
	valid := map[string][]string{
20
		"account":   {"expenses", "expenses:food", "a:b:c"},
21
		"commodity": {"$", "€", "USD", "US$"},
22
		"payee":     {"Grocery Store", "acme", "продукти"},
23
		"tag":       {"client", "a-b_c", "x123"},
24
	}
25
26
	invalid := map[string][]string{
27
		"account":   {"", " expenses", "expenses ", "expenses;x", "expenses\nx", "expenses\r"},
28
		"commodity": {"", " $", "$ ", "$;x", "$\n"},
29
		"payee":     {"", " payee", "payee|note", "payee;x"},
30
		"tag":       {"", "a:b", "a,b", "a b", "a;b", "tag\tx"},
31
	}
32
33
	validators := map[string]func(string) error{
34
		"account":   validateAccountName,
35
		"commodity": validateCommodityName,
36
		"payee":     validatePayeeName,
37
		"tag":       validateTagName,
38
	}
39
40
	for kind, v := range validators {
41
		for _, name := range valid[kind] {
42
			if err := v(name); err != nil {
43
				t.Errorf("%s %q: unexpected error %v", kind, name, err)
44
			}
45
		}
46
		for _, name := range invalid[kind] {
47
			if err := v(name); err == nil {
48
				t.Errorf("%s %q: expected error", kind, name)
49
			}
50
		}
51
	}
52
}
53
54
func TestAccountMatches(t *testing.T) {
55
	tests := map[string]bool{
56
		"expenses":        true,
57
		"expenses:food":   true,
58
		"expenses:food:l": true,
59
		"expenses2":       false,
60
		"ex":              false,
61
		"other":           false,
62
	}
63
	for name, want := range tests {
64
		if got := accountMatches(name, "expenses"); got != want {
65
			t.Errorf("accountMatches(%q, expenses) = %v, want %v", name, got, want)
66
		}
67
	}
68
}
69
70
// Golden
71
72
func TestRenameTxtar(t *testing.T) {
73
	for _, tt := range []string{"rename-account", "rename-commodity", "rename-payee", "rename-tag"} {
74
		ar := golden.Read(t, tt)
75
		t.Run(tt, func(t *testing.T) {
76
			h := newTxtarHarness(t, ar)
77
78
			newNames := strings.Split(strings.TrimRight(string(ar.Get("rename")), "\n"), "\n")
79
			if len(newNames) != len(h.cursors) {
80
				t.Fatalf("%d cursors, %d rename names", len(h.cursors), len(newNames))
81
			}
82
83
			var b strings.Builder
84
			for i := range h.cursors {
85
				tdp := h.textDocumentPosition(i)
86
				prep, err := h.srv.PrepareRename(t.Context(), &protocol.PrepareRenameParams{TextDocumentPositionParams: tdp})
87
				if err != nil {
88
					t.Fatal(err)
89
				}
90
				edit, err := h.srv.Rename(t.Context(), &protocol.RenameParams{
91
					TextDocumentPositionParams: tdp,
92
					NewName:                    newNames[i],
93
				})
94
				if err != nil {
95
					t.Fatal(err)
96
				}
97
98
				fmt.Fprintf(&b, "%d:%d\n", tdp.Position.Line, tdp.Position.Character)
99
				if p, ok := prep.(*protocol.PrepareRenamePlaceholder); ok {
100
					r := p.Range
101
					fmt.Fprintf(&b, "  prep in.journal %d:%d-%d:%d %q\n",
102
						r.Start.Line, r.Start.Character, r.End.Line, r.End.Character, p.Placeholder)
103
				} else {
104
					b.WriteString("  prep <none>\n")
105
				}
106
				if edit == nil {
107
					b.WriteString("  ren <none>\n")
108
					continue
109
				}
110
				uris := make([]string, 0, len(edit.Changes))
111
				for u := range edit.Changes {
112
					uris = append(uris, string(u))
113
				}
114
				sort.Strings(uris)
115
				for _, us := range uris {
116
					for _, e := range edit.Changes[uri.URI(us)] {
117
						r := e.Range
118
						fmt.Fprintf(&b, "  ren %s %d:%d-%d:%d %q\n", filepath.Base(uri.URI(us).Path()),
119
							r.Start.Line, r.Start.Character, r.End.Line, r.End.Character, e.NewText)
120
					}
121
				}
122
			}
123
			golden.Assert(t, ar, b.String())
124
		})
125
	}
126
}
127
128
// txtarHarness runs per-cursor lsp requests againt golden tests
129
type txtarHarness struct {
130
	srv     *server
131
	uri     uri.URI
132
	content string
133
	cursors []int
134
}
135
136
func newTxtarHarness(t *testing.T, ar *golden.Archive) *txtarHarness {
137
	t.Helper()
138
	dir := t.TempDir()
139
	for _, f := range ar.Files {
140
		if f.Name == "expect" || f.Name == "rename" {
141
			continue
142
		}
143
		if err := os.WriteFile(filepath.Join(dir, f.Name), f.Data, 0o644); err != nil {
144
			t.Fatal(err)
145
		}
146
	}
147
148
	content, cursors := stripCursors(string(ar.Get("in.journal")))
149
	if len(cursors) == 0 {
150
		t.Fatal("no '^' markers in in.journal")
151
	}
152
	u := uri.File(filepath.Join(dir, "in.journal"))
153
154
	srv := NewServer("test")
155
	srv.server.openDoc(u, content, 1, "journal")
156
157
	return &txtarHarness{srv: srv.server, uri: u, content: content, cursors: cursors}
158
}
159
160
func (h *txtarHarness) textDocumentPosition(i int) protocol.TextDocumentPositionParams {
161
	return protocol.TextDocumentPositionParams{
162
		TextDocument: protocol.TextDocumentIdentifier{URI: h.uri},
163
		Position:     lsputil.Position(h.content, h.cursors[i]),
164
	}
165
}
166
167
func stripCursors(content string) (string, []int) {
168
	var cursors []int
169
	for {
170
		i := strings.Index(content, "^")
171
		if i < 0 {
172
			return content, cursors
173
		}
174
		cursors = append(cursors, i)
175
		content = content[:i] + content[i+1:]
176
	}
177
}