// SPDX-License-Identifier: GPL-3.0-or-later package model import ( "context" "testing" ) // TestFuzzyMatch: characters in order match, out of order do not, and // capitals and accents are ignored the way a rule with (fold yes) is. func TestFuzzyMatch(t *testing.T) { yes := []struct{ pattern, text string }{ {"", "anything.pdf"}, {"lec", "lectio-2026-07-24.pdf"}, {"l24", "lectio-2026-07-24.pdf"}, {"LEC", "lectio-2026-07-24.pdf"}, {"zazolc", "zażółć gęślą jaźń.txt"}, {"faktura", "acme-ltd_faktura FV_12_2026.pdf"}, } for _, c := range yes { if _, ok := FuzzyMatch(c.pattern, c.text); !ok { t.Errorf("%q does not match %q", c.pattern, c.text) } } no := []struct{ pattern, text string }{ {"zzz", "lectio-2026-07-24.pdf"}, {"oitcel", "lectio.pdf"}, {"lectiox", "lectio.pdf"}, } for _, c := range no { if _, ok := FuzzyMatch(c.pattern, c.text); ok { t.Errorf("%q matches %q and should not", c.pattern, c.text) } } } // TestFuzzyScorePrefersTheObviousMatch: a name that starts with the pattern // scores above one where the characters are scattered. func TestFuzzyScorePrefersTheObviousMatch(t *testing.T) { close, ok1 := FuzzyMatch("lec", "lectio.pdf") far, ok2 := FuzzyMatch("lec", "old-latex-certificate.pdf") if !ok1 || !ok2 { t.Fatalf("both should match: %v %v", ok1, ok2) } if close <= far { t.Errorf("lectio.pdf scored %d, no better than the scattered match at %d", close, far) } } // TestPlanFilter: the filter leaves the rows that match, in the plan's // order; checking "all" then applies to those only, and the tab can say how // many checked files the filter is hiding. func TestPlanFilter(t *testing.T) { conf := "(path \"~/dl\")\n" + "(rule \"pdfs\" (when (type pdf)) (move \"Docs\") (stop))\n" + "(rule \"rest\" (move \"Other\"))\n" e, _ := sandboxDir(t, conf, map[string]string{ "lectio-one.pdf": "a", "lectio-two.pdf": "b", "notes.txt": "c", }) tab := planTab(t, e) all := tab.Matching("") if len(all) != 3 { t.Fatalf("an empty filter shows %d rows, want every one", len(all)) } shown := tab.Matching("lectio") if len(shown) != 2 { t.Fatalf("filter shows %+v, want the two lectio files", shown) } for i := 1; i < len(shown); i++ { if shown[i] <= shown[i-1] { t.Error("the filter reordered the plan") } } // A rule's name finds its files too. if rows := tab.Matching("pdfs"); len(rows) != 2 { t.Errorf("filtering by rule shows %+v, want the two pdfs", rows) } tab.SelectOnly(shown) if tab.SelectedCount() != 2 { t.Errorf("checked %d, want the two shown", tab.SelectedCount()) } for _, r := range tab.Rows { if r.Rel == "notes.txt" && r.Selected { t.Error("a row the filter hid was checked") } } // Now narrow the filter: one checked file is out of sight, and the tab // says so. narrow := tab.Matching("lectio-one") if n := tab.HiddenSelected(narrow); n != 1 { t.Errorf("HiddenSelected = %d, want 1", n) } if _, err := tab.Apply(context.Background()); err != nil { t.Fatal(err) } }