171 lines
6.4 KiB
Go
171 lines
6.4 KiB
Go
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)
|
|
}
|
|
}
|