9e0a98ed07
Validate(root): optional startup/test check for tree authoring faults — >1 slot child per node, empty word, duplicate sibling words, dead-end node — traversing circular slots without looping (#3). keypress subpackage: WaitForKey(ctx, cancel) cancels a context on any keystroke for watch-style streaming commands, with per-GOOS cbreak (linux TCGETS/TCSETS, BSD TIOCGETA/TIOCSETA) and a non-tty/unsupported fallback that just waits on ctx. Lifts the last OpenBSD-specific bit out of evpnc/maglevc's watch.go (#6). docs: replace PROPOSAL.md with an RFC-2119 design.md (FR/NFR for the library). Example now dogfoods Validate (a unit test) and keypress (a bounded `watch` command). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
57 lines
1.5 KiB
Go
57 lines
1.5 KiB
Go
// SPDX-FileCopyrightText: (C) Copyright 2026 Pim van Pelt <pim@ipng.ch>
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package cli
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestValidateClean checks that a well-formed tree (the builder fixture, which
|
|
// includes a circular slot) validates without error.
|
|
func TestValidateClean(t *testing.T) {
|
|
if err := Validate(buildFixtureB()); err != nil {
|
|
t.Errorf("Validate(clean tree) = %v, want nil", err)
|
|
}
|
|
}
|
|
|
|
// TestValidateFaults checks each fault class is reported.
|
|
func TestValidateFaults(t *testing.T) {
|
|
dyn := func(context.Context, fakeClient, []string) []string { return nil }
|
|
|
|
root := &Node[fakeClient]{Children: []*Node[fakeClient]{
|
|
// two slot children under one node
|
|
{Word: "twoslots", Help: "", Children: []*Node[fakeClient]{
|
|
{Word: "<a>", Dynamic: dyn, Run: runNoop},
|
|
{Word: "<b>", Dynamic: dyn, Run: runNoop},
|
|
}},
|
|
// duplicate fixed words
|
|
{Word: "dup", Children: []*Node[fakeClient]{
|
|
{Word: "x", Run: runNoop},
|
|
{Word: "x", Run: runNoop},
|
|
}},
|
|
// dead end: neither runs nor has children
|
|
{Word: "deadend"},
|
|
// empty word
|
|
{Word: "", Run: runNoop},
|
|
}}
|
|
|
|
err := Validate(root)
|
|
if err == nil {
|
|
t.Fatal("Validate(broken tree) = nil, want errors")
|
|
}
|
|
msg := err.Error()
|
|
for _, want := range []string{
|
|
"only the first is reachable",
|
|
`duplicate child word "x"`,
|
|
"dead end",
|
|
"empty Word",
|
|
} {
|
|
if !strings.Contains(msg, want) {
|
|
t.Errorf("Validate missing %q in:\n%s", want, msg)
|
|
}
|
|
}
|
|
}
|