summaryrefslogtreecommitdiff
path: root/cmd/krino/review_test.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 15:16:55 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-14 15:16:55 +0200
commit0b5d0eb92c5be2f0ddb2fa73990f31e5654e57fe (patch)
tree358e331945b8206ed4a72703e3aedfe8ea7cdcdb /cmd/krino/review_test.go
parent1d3f2d1e4c59867024470d3444e12698b7ebb22e (diff)
downloadkrino-0b5d0eb92c5be2f0ddb2fa73990f31e5654e57fe.tar.gz
krino-0b5d0eb92c5be2f0ddb2fa73990f31e5654e57fe.zip
krino: 0.0.5 — keyword cache, t and d in reviewv0.0.5
Diffstat (limited to 'cmd/krino/review_test.go')
-rw-r--r--cmd/krino/review_test.go90
1 files changed, 77 insertions, 13 deletions
diff --git a/cmd/krino/review_test.go b/cmd/krino/review_test.go
index a387022..19a1d01 100644
--- a/cmd/krino/review_test.go
+++ b/cmd/krino/review_test.go
@@ -22,7 +22,7 @@ func chains(rels ...string) []plan.Chain {
func TestChoosePerFile(t *testing.T) {
// c enters per-file mode, then y n y for three files.
- approved, action, err := reviewChains(strings.NewReader("cyny"), new(strings.Builder), chains("a", "b", "c"), "", palette{})
+ approved, _, action, err := reviewChains(strings.NewReader("cyny"), new(strings.Builder), chains("a", "b", "c"), "", palette{})
if err != nil {
t.Fatal(err)
}
@@ -35,31 +35,31 @@ func TestChoosePerFile(t *testing.T) {
}
func TestApplyAllAndSkip(t *testing.T) {
- approved, action, _ := reviewChains(strings.NewReader("a"), new(strings.Builder), chains("a", "b"), "", palette{})
+ approved, _, action, _ := reviewChains(strings.NewReader("a"), new(strings.Builder), chains("a", "b"), "", palette{})
if action != 'a' || len(approved) != 2 {
t.Errorf("[a] = %q %v; want every file approved", action, approved)
}
- approved, action, _ = reviewChains(strings.NewReader("s"), new(strings.Builder), chains("a", "b"), "", palette{})
+ approved, _, action, _ = reviewChains(strings.NewReader("s"), new(strings.Builder), chains("a", "b"), "", palette{})
if action != 's' || len(approved) != 0 {
t.Errorf("[s] = %q %v; want nothing approved", action, approved)
}
}
-func TestPerFileDoneStopsAsking(t *testing.T) {
- // c, y for the first, then d: apply what was chosen so far.
- approved, _, _ := reviewChains(strings.NewReader("cyd"), new(strings.Builder), chains("a", "b", "c"), "", palette{})
+func TestPerFileWriteStopsAsking(t *testing.T) {
+ // c, y for the first, then w: apply what was chosen so far.
+ approved, _, _, _ := reviewChains(strings.NewReader("cyw"), new(strings.Builder), chains("a", "b", "c"), "", palette{})
if !approved["a"] || approved["b"] || approved["c"] {
t.Errorf("approved = %v; want only a", approved)
}
}
// TestPerFileQuitAppliesNothing: spec §8.3's [q] on the per-file prompt is
-// "quit, apply nothing" - stronger than [d], which keeps what was already
+// "quit, apply nothing" - stronger than [w], which keeps what was already
// chosen. It folds into the same top-level 'q' the caller already handles
// for the directory-level menu (spec §8.2's [q]), and discards even a file
// already marked yes.
func TestPerFileQuitAppliesNothing(t *testing.T) {
- approved, action, err := reviewChains(strings.NewReader("cyq"), new(strings.Builder), chains("a", "b"), "", palette{})
+ approved, _, action, err := reviewChains(strings.NewReader("cyq"), new(strings.Builder), chains("a", "b"), "", palette{})
if err != nil {
t.Fatal(err)
}
@@ -74,7 +74,7 @@ func TestPerFileQuitAppliesNothing(t *testing.T) {
// TestPerFileYesToAllRemaining: spec §8.3's [a] mid-review approves the
// current file and every remaining one without asking again.
func TestPerFileYesToAllRemaining(t *testing.T) {
- approved, action, err := reviewChains(strings.NewReader("ca"), new(strings.Builder), chains("a", "b", "c"), "", palette{})
+ approved, _, action, err := reviewChains(strings.NewReader("ca"), new(strings.Builder), chains("a", "b", "c"), "", palette{})
if err != nil {
t.Fatal(err)
}
@@ -88,7 +88,7 @@ func TestPerFileYesToAllRemaining(t *testing.T) {
// same prompt is read again.
func TestInvalidKeyReprompts(t *testing.T) {
out := new(strings.Builder)
- approved, action, err := reviewChains(strings.NewReader("zs"), out, chains("a"), "", palette{})
+ approved, _, action, err := reviewChains(strings.NewReader("zs"), out, chains("a"), "", palette{})
if err != nil {
t.Fatal(err)
}
@@ -115,7 +115,7 @@ func TestPerFileDestinationIsRootRelative(t *testing.T) {
{File: scan.File{Rel: "outside.txt"}, Steps: []plan.Step{{Kind: plan.Move, Dst: "/home/x/backup/outside.txt"}}},
}
out := new(strings.Builder)
- if _, _, err := reviewChains(strings.NewReader("cyy"), out, cs, root, palette{}); err != nil {
+ if _, _, _, err := reviewChains(strings.NewReader("cyy"), out, cs, root, palette{}); err != nil {
t.Fatal(err)
}
text := out.String()
@@ -134,7 +134,7 @@ func TestPerFileShowsTheWholeBlock(t *testing.T) {
{Kind: plan.Move, Rule: "acme", Dst: "/w/a.pdf", Reason: `content "acme ltd"`},
}}}
out := new(strings.Builder)
- if _, _, err := reviewChains(strings.NewReader("cy"), out, cs, "", palette{}); err != nil {
+ if _, _, _, err := reviewChains(strings.NewReader("cy"), out, cs, "", palette{}); err != nil {
t.Fatal(err)
}
want := "\n[1/1] a.pdf\n move → /w/\n rule acme\n because content \"acme ltd\"\n"
@@ -154,7 +154,7 @@ func TestPerFileWrapsToTheTerminal(t *testing.T) {
{Kind: plan.Move, Rule: "acme", Dst: "/w/some/deeply/nested/destination/directory/for/invoices/a.pdf", Reason: `content "acme ltd"`},
}}}
out := new(strings.Builder)
- if _, _, err := reviewChains(strings.NewReader("cy"), out, cs, "", palette{}); err != nil {
+ if _, _, _, err := reviewChains(strings.NewReader("cy"), out, cs, "", palette{}); err != nil {
t.Fatal(err)
}
var prompt string
@@ -170,3 +170,67 @@ func TestPerFileWrapsToTheTerminal(t *testing.T) {
t.Errorf("wrapped prompt reads %q, want %q\n%s", prompt, perFileKeys, out)
}
}
+
+// TestPerFileTrashAndDelete: [t] and [d] (confirmed with y) replace what the
+// rules planned for that file with one step, and approve it.
+func TestPerFileTrashAndDelete(t *testing.T) {
+ out := new(strings.Builder)
+ approved, replaced, action, err := reviewChains(strings.NewReader("ctdyn"), out, chains("a", "b", "c"), "", palette{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if action != 'c' || !approved["a"] || !approved["b"] || approved["c"] {
+ t.Errorf("approved = %v action = %q; want a and b", approved, action)
+ }
+ if len(replaced) != 2 || replaced["a"] != plan.Trash || replaced["b"] != plan.DeletePermanent {
+ t.Errorf("replaced = %v; want a trash, b deleted permanently", replaced)
+ }
+ if !strings.Contains(out.String(), "delete b permanently? [y/N]") {
+ t.Errorf("no confirmation asked:\n%s", out)
+ }
+}
+
+// TestPerFileDeleteNeedsConfirmation: any key but y after [d] deletes
+// nothing and asks about the same file again.
+func TestPerFileDeleteNeedsConfirmation(t *testing.T) {
+ out := new(strings.Builder)
+ approved, replaced, _, err := reviewChains(strings.NewReader("cdny"), out, chains("a"), "", palette{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(replaced) != 0 || !approved["a"] {
+ t.Errorf("approved = %v replaced = %v; want a approved as planned, nothing replaced", approved, replaced)
+ }
+ if strings.Count(out.String(), perFileKeys) != 2 {
+ t.Errorf("the prompt should be shown again after a cancelled delete:\n%s", out)
+ }
+}
+
+// TestPerFileQuitDiscardsReplacements: [q] after [t] applies nothing, the
+// trash included.
+func TestPerFileQuitDiscardsReplacements(t *testing.T) {
+ approved, replaced, action, _ := reviewChains(strings.NewReader("ctq"), new(strings.Builder), chains("a", "b"), "", palette{})
+ if action != 'q' || len(approved) != 0 || len(replaced) != 0 {
+ t.Errorf("approved = %v replaced = %v action = %q; want nothing", approved, replaced, action)
+ }
+}
+
+// TestReplaceChains: a replaced file's chain becomes one step of the chosen
+// kind on the file itself, under the rule name (review); other chains keep
+// their steps.
+func TestReplaceChains(t *testing.T) {
+ cs := chains("a", "b")
+ cs[0].File.Path = "/dl/a"
+ cs[0].Steps = append(cs[0].Steps, plan.Step{Kind: plan.Copy, Dst: "/w/copy"})
+ got := replaceChains(cs, map[string]plan.Kind{"a": plan.DeletePermanent})
+ want := plan.Step{Kind: plan.DeletePermanent, Rule: "(review)", Src: "/dl/a", Reason: "chosen in review"}
+ if len(got[0].Steps) != 1 || got[0].Steps[0] != want {
+ t.Errorf("a's steps = %+v; want only %+v", got[0].Steps, want)
+ }
+ if len(got[1].Steps) != 1 || got[1].Steps[0].Kind != plan.Move {
+ t.Errorf("b's steps changed: %+v", got[1].Steps)
+ }
+ if len(cs[0].Steps) != 2 {
+ t.Errorf("replaceChains changed its input: %+v", cs[0].Steps)
+ }
+}