package autocorrect import ( "os" "path/filepath" "strings" "testing" ) func TestLoadValidRules(t *testing.T) { dir := t.TempDir() path := writeAutocorrect(t, dir, `autocorrect: - target: "Hrank" match: - "Frank" - "frank" `) rules, err := Load(path) if err != nil { t.Fatalf("load rules: %v", err) } got, count := rules.Apply("Frank and frank") if got != "Hrank and Hrank" { t.Fatalf("text = %q, want %q", got, "Hrank and Hrank") } if count != 2 { t.Fatalf("count = %d, want 2", count) } } func TestLoadValidation(t *testing.T) { tests := []struct { name string content string want string }{ { name: "missing top-level autocorrect", content: `other: []`, want: "must contain at least one autocorrect rule", }, { name: "empty rules list", content: `autocorrect: []`, want: "must contain at least one autocorrect rule", }, { name: "empty target", content: `autocorrect: - target: "" match: ["Frank"] `, want: "must include target", }, { name: "empty match list", content: `autocorrect: - target: "Hrank" match: [] `, want: "must include at least one match string", }, { name: "empty match string", content: `autocorrect: - target: "Hrank" match: [" "] `, want: "contains empty match string", }, { name: "duplicate match across rules", content: `autocorrect: - target: "Hrank" match: ["Frank"] - target: "Other" match: ["Frank"] `, want: `appears in both rule 0 and rule 1`, }, { name: "duplicate match within rule", content: `autocorrect: - target: "Hrank" match: ["Frank", "Frank"] `, want: `contains duplicate match string "Frank"`, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { dir := t.TempDir() path := writeAutocorrect(t, dir, test.content) _, err := Load(path) if err == nil { t.Fatal("expected error") } if !strings.Contains(err.Error(), test.want) { t.Fatalf("expected error to contain %q, got %v", test.want, err) } }) } } func TestApplyReplacementBehavior(t *testing.T) { rules := Rules{rules: []Rule{ { Target: "Hrank", Match: []string{"Frank"}, }, { Target: "Mike Brown", Match: []string{"Mike Pat"}, }, { Target: "Godfrey", Match: []string{"God-free"}, }, }} tests := []struct { name string input string want string wantCount int }{ { name: "case sensitive", input: "Frank and FRANK", want: "Hrank and FRANK", wantCount: 1, }, { name: "punctuation boundary", input: "Frank, are you there?", want: "Hrank, are you there?", wantCount: 1, }, { name: "no substring in larger token", input: "Franklin and xFrank Frank_y Frank2", want: "Franklin and xFrank Frank_y Frank2", wantCount: 0, }, { name: "multi word match", input: "Hello Mike Pat.", want: "Hello Mike Brown.", wantCount: 1, }, { name: "hyphenated match", input: "God-free is here.", want: "Godfrey is here.", wantCount: 1, }, { name: "hyphen outside match is boundary", input: "x-Frank-y", want: "x-Hrank-y", wantCount: 1, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { got, count := rules.Apply(test.input) if got != test.want { t.Fatalf("text = %q, want %q", got, test.want) } if count != test.wantCount { t.Fatalf("count = %d, want %d", count, test.wantCount) } }) } } func writeAutocorrect(t *testing.T, dir string, content string) string { t.Helper() path := filepath.Join(dir, "autocorrect.yml") if err := os.WriteFile(path, []byte(content), 0o600); err != nil { t.Fatalf("write autocorrect file: %v", err) } return path }