first commit

This commit is contained in:
2026-08-14 23:41:57 +08:00
commit 086803a8dd
471 changed files with 91938 additions and 0 deletions
+170
View File
@@ -0,0 +1,170 @@
package runtime
// Tests for the prompt-template expansion engine (US-002, #332). Covers the
// positional/default/slice syntax from the acceptance criteria, the
// ${...}-before-bare-$ ordering invariant, and the no-placeholder append
// behavior that preserves ParseUserCommand's existing semantics.
import "testing"
func TestExpandTemplateNoPlaceholder(t *testing.T) {
// No args: verbatim.
if got := ExpandTemplate("Take a note", nil); got != "Take a note" {
t.Errorf("no args: got %q", got)
}
if got := ExpandTemplate("Take a note", []string{}); got != "Take a note" {
t.Errorf("empty args: got %q", got)
}
// With args: appended after a blank line, joined by spaces.
if got := ExpandTemplate("Take a note", []string{"buy", "milk"}); got != "Take a note\n\nbuy milk" {
t.Errorf("with args: got %q", got)
}
}
func TestExpandTemplatePositional(t *testing.T) {
args := []string{"Button", "click", "handler"}
if got := ExpandTemplate("$1", args); got != "Button" {
t.Errorf("$1 = %q, want Button", got)
}
if got := ExpandTemplate("$3", args); got != "handler" {
t.Errorf("$3 = %q, want handler", got)
}
// Out of range -> empty.
if got := ExpandTemplate("$5", args); got != "" {
t.Errorf("$5 = %q, want empty", got)
}
// Multi-digit.
if got := ExpandTemplate("$10", []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}); got != "j" {
t.Errorf("$10 = %q, want j", got)
}
}
func TestExpandTemplateAllArgs(t *testing.T) {
args := []string{"Button", "click", "handler"}
if got := ExpandTemplate("$@", args); got != "Button click handler" {
t.Errorf("$@ = %q", got)
}
if got := ExpandTemplate("$ARGUMENTS", args); got != "Button click handler" {
t.Errorf("$ARGUMENTS = %q", got)
}
}
func TestExpandTemplateDefaultPositional(t *testing.T) {
// Present and non-empty -> the arg.
if got := ExpandTemplate("${1:-default}", []string{"a"}); got != "a" {
t.Errorf("present: got %q", got)
}
// Absent -> default.
if got := ExpandTemplate("${1:-default}", nil); got != "default" {
t.Errorf("absent: got %q", got)
}
// Present but empty -> default.
if got := ExpandTemplate("${1:-default}", []string{""}); got != "default" {
t.Errorf("empty: got %q", got)
}
// Default containing a $ is not re-expanded.
if got := ExpandTemplate("${1:-pay $5}", nil); got != "pay $5" {
t.Errorf("default literal $: got %q", got)
}
}
func TestExpandTemplateDefaultAllArgs(t *testing.T) {
if got := ExpandTemplate("${@:-default}", []string{"a", "b"}); got != "a b" {
t.Errorf("present: got %q", got)
}
if got := ExpandTemplate("${@:-default}", nil); got != "default" {
t.Errorf("absent: got %q", got)
}
if got := ExpandTemplate("${ARGUMENTS:-default}", []string{"x"}); got != "x" {
t.Errorf("ARGUMENTS present: got %q", got)
}
if got := ExpandTemplate("${ARGUMENTS:-default}", nil); got != "default" {
t.Errorf("ARGUMENTS absent: got %q", got)
}
}
func TestExpandTemplateSlice(t *testing.T) {
args := []string{"a", "b", "c", "d", "e"}
// ${@:N}: from Nth onward.
if got := ExpandTemplate("${@:2}", args); got != "b c d e" {
t.Errorf("${@:2} = %q", got)
}
if got := ExpandTemplate("${@:1}", args); got != "a b c d e" {
t.Errorf("${@:1} = %q", got)
}
// ${@:N:L}: L args starting at N.
if got := ExpandTemplate("${@:2:2}", args); got != "b c" {
t.Errorf("${@:2:2} = %q", got)
}
if got := ExpandTemplate("${@:1:3}", args); got != "a b c" {
t.Errorf("${@:1:3} = %q", got)
}
// N beyond args -> empty.
if got := ExpandTemplate("${@:9}", args); got != "" {
t.Errorf("${@:9} = %q, want empty", got)
}
// L clamps to available.
if got := ExpandTemplate("${@:3:100}", args); got != "c d e" {
t.Errorf("${@:3:100} = %q, want c d e", got)
}
// N<1 -> empty.
if got := ExpandTemplate("${@:0}", args); got != "" {
t.Errorf("${@:0} = %q, want empty", got)
}
}
func TestExpandTemplateOrderingNoDoubleMatch(t *testing.T) {
// A bare $1 must NOT match inside ${1:-...}. With args present, ${1:-$2}
// uses arg1 ("a"); the $2 default is never consulted nor expanded.
if got := ExpandTemplate("${1:-$2}", []string{"a"}); got != "a" {
t.Errorf("${1:-$2} with arg1=a: got %q, want a", got)
}
// With arg1 absent, the default "$2" is taken literally (not expanded to "").
if got := ExpandTemplate("${1:-$2}", nil); got != "$2" {
t.Errorf("${1:-$2} absent: got %q, want literal $2", got)
}
// Braced and bare coexist in one pass.
if got := ExpandTemplate("${1:-x} and $2", []string{"a", "b"}); got != "a and b" {
t.Errorf("coexist: got %q", got)
}
}
func TestExpandTemplateExampleFromSpec(t *testing.T) {
// $@ expands to ALL args joined (AC: "all args joined by a single space").
if got := ExpandTemplate("echo $@", []string{"a", "b", "c"}); got != "echo a b c" {
t.Errorf("$@ all-args: got %q", got)
}
// The pi "component" example: $1 for the name, features are the remaining
// args expressed with the slice form ${@:2} (the right tool for "rest").
tmpl := "Create a React component named $1 with features: ${@:2}"
if got := ExpandTemplate(tmpl, []string{"Button", "click", "handler"}); got != "Create a React component named Button with features: click handler" {
t.Errorf("component example: got %q", got)
}
// "Summarize the current state in ${1:-7} bullet points."
bullets := "Summarize the current state in ${1:-7} bullet points."
if got := ExpandTemplate(bullets, nil); got != "Summarize the current state in 7 bullet points." {
t.Errorf("default bullets: got %q", got)
}
if got := ExpandTemplate(bullets, []string{"5"}); got != "Summarize the current state in 5 bullet points." {
t.Errorf("explicit bullets: got %q", got)
}
}
func TestExpandTemplateLiteralDollar(t *testing.T) {
// A '$' not forming a placeholder is emitted literally. Such a template has
// no placeholder, so args (if any) are appended per the no-placeholder rule.
if got := ExpandTemplate("100$ off", nil); got != "100$ off" {
t.Errorf("literal $ mid-string: got %q", got)
}
if got := ExpandTemplate("trailing$", nil); got != "trailing$" {
t.Errorf("trailing $ no args: got %q", got)
}
// No placeholder + args -> append after a blank line.
if got := ExpandTemplate("trailing$", []string{"x"}); got != "trailing$\n\nx" {
t.Errorf("trailing $ with args: got %q", got)
}
// A literal '$' alongside a real placeholder: '$' before a space stays '$'.
if got := ExpandTemplate("cost $ and $1", []string{"five"}); got != "cost $ and five" {
t.Errorf("literal $ next to placeholder: got %q", got)
}
}