mirror of
https://github.com/boolean-maybe/tiki
synced 2026-04-21 13:37:20 +00:00
340 lines
8.5 KiB
Go
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)
|
|
}
|
|
}
|