package model import ( "os" "path/filepath" "regexp" "strings" "testing" ) // TestReservedKeysCoversHandleKey is a drift guard: every `case "..."` clause in // the main handleKey switch (keys.go) must be represented in coreReservedKeys. // If this fails after editing keys.go, add the missing key to coreReservedKeys // in keymap.go. The test deliberately skips case strings containing "+" with // keys like "shift+up" which *are* included — it scans all quoted tokens. func TestReservedKeysCoversHandleKey(t *testing.T) { path := filepath.Join("keys.go") data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } // Find every `case "X", "Y", ...:` clause in keys.go. We intentionally // over-collect (subhandler switches too) and then filter to the main // handler's section between "func (m *Model) handleKey" and its close. src := string(data) // The main dispatch switch is anchored by its comment header; overlays // and subhandlers have their own switches with different anchors. start := strings.Index(src, "// Vim-style count prefix") if start < 0 { t.Fatal("could not locate main dispatch anchor in keys.go") } // Bound at the next top-level function declaration to avoid scanning // into helper functions below handleKey. body := src[start:] if end := strings.Index(body, "\nfunc "); end > 0 { body = body[:end] } caseRe := regexp.MustCompile(`case ("[^"]+"(?:, "[^"]+")*):`) tokenRe := regexp.MustCompile(`"([^"]+)"`) reserved := ReservedKeys() var missing []string for _, m := range caseRe.FindAllStringSubmatch(body, -1) { for _, tok := range tokenRe.FindAllStringSubmatch(m[1], -1) { key := tok[1] if !reserved[key] { missing = append(missing, key) } } } if len(missing) > 0 { t.Fatalf("handleKey has case clauses not covered by coreReservedKeys: %v\nAdd these to keymap.go so plugin binds can't shadow them.", missing) } }