tiki/workflow/status_test.go
2026-04-16 15:35:28 -04:00

340 lines
8.5 KiB
Go

package workflow
import "testing"
func defaultTestStatuses() []StatusDef {
return []StatusDef{
{Key: "backlog", Label: "Backlog", Emoji: "📥", Default: true},
{Key: "ready", Label: "Ready", Emoji: "📋", Active: true},
{Key: "inProgress", Label: "In Progress", Emoji: "⚙️", Active: true},
{Key: "review", Label: "Review", Emoji: "👀", Active: true},
{Key: "done", Label: "Done", Emoji: "✅", Done: true},
}
}
func mustBuildStatusRegistry(t *testing.T, defs []StatusDef) *StatusRegistry {
t.Helper()
reg, err := NewStatusRegistry(defs)
if err != nil {
t.Fatalf("NewStatusRegistry: %v", err)
}
return reg
}
func TestNormalizeStatusKey(t *testing.T) {
tests := []struct {
input string
want StatusKey
}{
{"backlog", "backlog"},
{"BACKLOG", "backlog"},
{"In-Progress", "inProgress"},
{"in progress", "inProgress"},
{" DONE ", "done"},
{"In_Review", "inReview"},
{"in_progress", "inProgress"},
{"IN_PROGRESS", "inProgress"},
{"", ""},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
if got := NormalizeStatusKey(tt.input); got != tt.want {
t.Errorf("NormalizeStatusKey(%q) = %q, want %q", tt.input, got, tt.want)
}
})
}
}
func TestNewStatusRegistry_DefaultStatuses(t *testing.T) {
reg := mustBuildStatusRegistry(t, defaultTestStatuses())
if len(reg.All()) != 5 {
t.Fatalf("expected 5 statuses, got %d", len(reg.All()))
}
if reg.DefaultKey() != "backlog" {
t.Errorf("expected default key 'backlog', got %q", reg.DefaultKey())
}
if reg.DoneKey() != "done" {
t.Errorf("expected done key 'done', got %q", reg.DoneKey())
}
}
func TestNewStatusRegistry_CustomStatuses(t *testing.T) {
custom := []StatusDef{
{Key: "new", Label: "New", Emoji: "🆕", Default: true},
{Key: "wip", Label: "Work In Progress", Emoji: "🔧", Active: true},
{Key: "closed", Label: "Closed", Emoji: "🔒", Done: true},
}
reg := mustBuildStatusRegistry(t, custom)
if len(reg.All()) != 3 {
t.Fatalf("expected 3 statuses, got %d", len(reg.All()))
}
if reg.DefaultKey() != "new" {
t.Errorf("expected default key 'new', got %q", reg.DefaultKey())
}
if reg.DoneKey() != "closed" {
t.Errorf("expected done key 'closed', got %q", reg.DoneKey())
}
}
func TestStatusRegistry_IsValid(t *testing.T) {
reg := mustBuildStatusRegistry(t, defaultTestStatuses())
tests := []struct {
key string
want bool
}{
{"backlog", true},
{"ready", true},
{"inProgress", true},
{"In-Progress", true},
{"review", true},
{"done", true},
{"unknown", false},
{"", false},
{"todo", false},
}
for _, tt := range tests {
t.Run(tt.key, func(t *testing.T) {
if got := reg.IsValid(tt.key); got != tt.want {
t.Errorf("IsValid(%q) = %v, want %v", tt.key, got, tt.want)
}
})
}
}
func TestStatusRegistry_IsActive(t *testing.T) {
reg := mustBuildStatusRegistry(t, defaultTestStatuses())
tests := []struct {
key string
want bool
}{
{"backlog", false},
{"ready", true},
{"inProgress", true},
{"review", true},
{"done", false},
}
for _, tt := range tests {
t.Run(tt.key, func(t *testing.T) {
if got := reg.IsActive(tt.key); got != tt.want {
t.Errorf("IsActive(%q) = %v, want %v", tt.key, got, tt.want)
}
})
}
}
func TestStatusRegistry_IsDone(t *testing.T) {
reg := mustBuildStatusRegistry(t, defaultTestStatuses())
if !reg.IsDone("done") {
t.Error("expected 'done' to be marked as done")
}
if reg.IsDone("backlog") {
t.Error("expected 'backlog' to not be marked as done")
}
}
func TestStatusRegistry_Lookup(t *testing.T) {
reg := mustBuildStatusRegistry(t, defaultTestStatuses())
def, ok := reg.Lookup("ready")
if !ok {
t.Fatal("expected to find 'ready'")
return
}
if def.Label != "Ready" {
t.Errorf("expected label 'Ready', got %q", def.Label)
}
if def.Emoji != "📋" {
t.Errorf("expected emoji '📋', got %q", def.Emoji)
}
_, ok = reg.Lookup("nonexistent")
if ok {
t.Error("expected Lookup to return false for nonexistent key")
}
}
func TestStatusRegistry_Keys(t *testing.T) {
reg := mustBuildStatusRegistry(t, defaultTestStatuses())
keys := reg.Keys()
expected := []StatusKey{"backlog", "ready", "inProgress", "review", "done"}
if len(keys) != len(expected) {
t.Fatalf("expected %d keys, got %d", len(expected), len(keys))
}
for i, key := range keys {
if key != expected[i] {
t.Errorf("keys[%d] = %q, want %q", i, key, expected[i])
}
}
}
func TestStatusRegistry_RejectsNonCanonicalKeys(t *testing.T) {
_, err := NewStatusRegistry([]StatusDef{
{Key: "In-Progress", Label: "In Progress", Default: true},
{Key: "done", Label: "Done", Done: true},
})
if err == nil {
t.Fatal("expected error for non-canonical key")
}
}
func TestStatusRegistry_CanonicalKeysWork(t *testing.T) {
reg := mustBuildStatusRegistry(t, defaultTestStatuses())
if !reg.IsValid("inProgress") {
t.Error("expected 'inProgress' to be valid")
}
// lookup normalizes input — "In-Progress" splits on separator to get "inProgress"
if !reg.IsValid("In-Progress") {
t.Error("expected 'In-Progress' to be valid via normalization")
}
}
func TestNewStatusRegistry_EmptyKey(t *testing.T) {
defs := []StatusDef{
{Key: "", Label: "No Key"},
}
_, err := NewStatusRegistry(defs)
if err == nil {
t.Error("expected error for empty key")
}
}
func TestNewStatusRegistry_DuplicateKey(t *testing.T) {
defs := []StatusDef{
{Key: "ready", Label: "Ready", Default: true},
{Key: "ready", Label: "Ready 2"},
}
_, err := NewStatusRegistry(defs)
if err == nil {
t.Error("expected error for duplicate key")
}
}
func TestNewStatusRegistry_Empty(t *testing.T) {
_, err := NewStatusRegistry(nil)
if err == nil {
t.Error("expected error for empty statuses")
}
}
func TestNewStatusRegistry_RequiresDefault(t *testing.T) {
defs := []StatusDef{
{Key: "alpha", Label: "Alpha", Done: true},
{Key: "beta", Label: "Beta"},
}
_, err := NewStatusRegistry(defs)
if err == nil {
t.Fatal("expected error when no status is marked default")
}
}
func TestNewStatusRegistry_RequiresDone(t *testing.T) {
defs := []StatusDef{
{Key: "alpha", Label: "Alpha", Default: true},
{Key: "beta", Label: "Beta"},
}
_, err := NewStatusRegistry(defs)
if err == nil {
t.Fatal("expected error when no status is marked done")
}
}
func TestStatusRegistry_AllReturnsCopy(t *testing.T) {
reg := mustBuildStatusRegistry(t, defaultTestStatuses())
all := reg.All()
all[0].Key = "mutated"
// internal state must be unchanged
keys := reg.Keys()
if keys[0] != "backlog" {
t.Errorf("All() mutation leaked into registry: first key = %q, want %q", keys[0], "backlog")
}
}
func TestNewStatusRegistry_MultipleDoneErrors(t *testing.T) {
defs := []StatusDef{
{Key: "alpha", Label: "Alpha", Default: true, Done: true},
{Key: "beta", Label: "Beta", Done: true},
}
_, err := NewStatusRegistry(defs)
if err == nil {
t.Fatal("expected error for multiple done statuses")
}
}
func TestNewStatusRegistry_MultipleDefaultErrors(t *testing.T) {
defs := []StatusDef{
{Key: "alpha", Label: "Alpha", Default: true},
{Key: "beta", Label: "Beta", Default: true, Done: true},
}
_, err := NewStatusRegistry(defs)
if err == nil {
t.Fatal("expected error for multiple default statuses")
}
}
func TestNewStatusRegistry_DuplicateDisplay(t *testing.T) {
defs := []StatusDef{
{Key: "alpha", Label: "Open", Emoji: "🟢", Default: true},
{Key: "beta", Label: "Open", Emoji: "🟢", Done: true},
}
_, err := NewStatusRegistry(defs)
if err == nil {
t.Fatal("expected error for duplicate display")
}
}
func TestNewStatusRegistry_LabelDefaultsToKey(t *testing.T) {
defs := []StatusDef{
{Key: "alpha", Emoji: "🔵", Default: true},
{Key: "beta", Emoji: "🔴", Done: true},
}
reg, err := NewStatusRegistry(defs)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
def, _ := reg.Lookup("alpha")
if def.Label != "alpha" {
t.Errorf("expected label to default to key, got %q", def.Label)
}
}
func TestNewStatusRegistry_EmptyWhitespaceLabel(t *testing.T) {
defs := []StatusDef{
{Key: "alpha", Label: " ", Default: true},
{Key: "beta", Done: true},
}
_, err := NewStatusRegistry(defs)
if err == nil {
t.Fatal("expected error for whitespace-only label")
}
}
func TestStatusKeyConstants(t *testing.T) {
if StatusBacklog != "backlog" {
t.Errorf("StatusBacklog = %q", StatusBacklog)
}
if StatusReady != "ready" {
t.Errorf("StatusReady = %q", StatusReady)
}
if StatusInProgress != "inProgress" {
t.Errorf("StatusInProgress = %q", StatusInProgress)
}
if StatusReview != "review" {
t.Errorf("StatusReview = %q", StatusReview)
}
if StatusDone != "done" {
t.Errorf("StatusDone = %q", StatusDone)
}
}