Add a tags screen
Screen 8 lists every distinct tag -- the ones the index holds and the ones rules.toml names -- with how many transactions carry it and the rules that write it, each by file position with the transactions it wins. It is where near-duplicate tags and tags several rules feed show up, and a rule beaten by a more specific one reads (0) there as it does on the rules screen. A tag no rule names any more says the next retag clears it. The transfer label is not a tag and is never listed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -461,3 +461,56 @@ to_desc = "*FROM CHECKING*"
|
||||
t.Errorf("oldest leg = %+v", oldest)
|
||||
}
|
||||
}
|
||||
|
||||
// The tags screen lists each tag once, with the transactions carrying it and
|
||||
// every rule that writes it — a shadowed one at zero — and never the
|
||||
// transfer label, which no rule wrote.
|
||||
func TestTagListGroupsRulesByTag(t *testing.T) {
|
||||
_, h := newTestServer(t, `
|
||||
[[rule]]
|
||||
match = "*LIDL*"
|
||||
tag = "groceries"
|
||||
|
||||
[[rule]]
|
||||
match = "*KAUFLAND*"
|
||||
tag = "groceries"
|
||||
|
||||
[[rule]]
|
||||
match = "*LIDL SOFIA*"
|
||||
tag = "groceries"
|
||||
|
||||
[[rule]]
|
||||
match = "*ZARA*"
|
||||
tag = "clothes"
|
||||
|
||||
[[transfer]]
|
||||
from_account = "checking"
|
||||
from_desc = "*TO SAVINGS*"
|
||||
to_account = "savings"
|
||||
to_desc = "*FROM CHECKING*"
|
||||
`,
|
||||
fixtureTxn{"checking", "2026-02-01", "LIDL SOFIA", -1000},
|
||||
fixtureTxn{"checking", "2026-02-02", "KAUFLAND", -500},
|
||||
fixtureTxn{"checking", "2026-02-03", "TO SAVINGS", -100},
|
||||
fixtureTxn{"savings", "2026-02-03", "FROM CHECKING", 100},
|
||||
)
|
||||
var res struct{ Tags []tagEntry }
|
||||
call(t, h, "GET", "/api/tags", nil, http.StatusOK, &res)
|
||||
if len(res.Tags) != 2 || res.Tags[0].Tag != "clothes" || res.Tags[1].Tag != "groceries" {
|
||||
t.Fatalf("tags = %+v, want clothes and groceries only", res.Tags)
|
||||
}
|
||||
clothes, groceries := res.Tags[0], res.Tags[1]
|
||||
if clothes.Transactions != 0 || len(clothes.Rules) != 1 {
|
||||
t.Errorf("clothes = %+v, want its rule and no transactions", clothes)
|
||||
}
|
||||
if groceries.Transactions != 2 || len(groceries.Rules) != 3 {
|
||||
t.Fatalf("groceries = %+v, want 2 transactions from 3 rules", groceries)
|
||||
}
|
||||
usage := map[string]int{}
|
||||
for _, r := range groceries.Rules {
|
||||
usage[r.Pattern] = r.Usage
|
||||
}
|
||||
if usage["*LIDL*"] != 0 || usage["*LIDL SOFIA*"] != 1 || usage["*KAUFLAND*"] != 1 {
|
||||
t.Errorf("usage = %v, want *LIDL* shadowed by *LIDL SOFIA*", usage)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user