- TestComputeTabloProgress_{Empty,AllDone,Half,EtapesIgnored}
- TestNewTabloDetailViewModel_{GroupsTasksByStatus,EtapesExcludedFromColumns,EtapesPopulated}
- TestGetTabloDetailPage_{Returns200,Returns404,Returns400,Unauthenticated}
- TestTabloDetailKanbanColumns
- TestGetTabloDetailPage_ContainsSortableScript
244 lines
6 KiB
Go
244 lines
6 KiB
Go
package views
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
tablomodel "xtablo-backend/internal/tablos"
|
|
taskmodel "xtablo-backend/internal/tasks"
|
|
)
|
|
|
|
func makeTask(status taskmodel.Status, isEtape bool) taskmodel.Record {
|
|
return taskmodel.Record{
|
|
ID: uuid.New(),
|
|
TabloID: uuid.New(),
|
|
OwnerID: uuid.New(),
|
|
Title: "Task " + string(status),
|
|
Status: status,
|
|
IsEtape: isEtape,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
}
|
|
|
|
func makeTablo() tablomodel.Record {
|
|
return tablomodel.Record{
|
|
ID: uuid.New(),
|
|
OwnerID: uuid.New(),
|
|
Name: "Test Tablo",
|
|
Color: "#3B82F6",
|
|
Status: tablomodel.StatusTodo,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
}
|
|
|
|
func TestComputeTabloProgress_Empty(t *testing.T) {
|
|
got := computeTabloProgress([]taskmodel.Record{})
|
|
if got != 0 {
|
|
t.Fatalf("expected 0, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestComputeTabloProgress_AllDone(t *testing.T) {
|
|
tasks := []taskmodel.Record{
|
|
makeTask(taskmodel.StatusDone, false),
|
|
makeTask(taskmodel.StatusDone, false),
|
|
}
|
|
got := computeTabloProgress(tasks)
|
|
if got != 100 {
|
|
t.Fatalf("expected 100, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestComputeTabloProgress_Half(t *testing.T) {
|
|
tasks := []taskmodel.Record{
|
|
makeTask(taskmodel.StatusDone, false),
|
|
makeTask(taskmodel.StatusTodo, false),
|
|
}
|
|
got := computeTabloProgress(tasks)
|
|
if got != 50 {
|
|
t.Fatalf("expected 50, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestComputeTabloProgress_EtapesIgnored(t *testing.T) {
|
|
tasks := []taskmodel.Record{
|
|
makeTask(taskmodel.StatusDone, false),
|
|
// etape task — should be excluded from denominator and numerator
|
|
makeTask(taskmodel.StatusTodo, true),
|
|
}
|
|
got := computeTabloProgress(tasks)
|
|
if got != 100 {
|
|
t.Fatalf("expected 100 (etape ignored), got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestNewTabloDetailViewModel_GroupsTasksByStatus(t *testing.T) {
|
|
tablo := makeTablo()
|
|
|
|
todoTask := makeTask(taskmodel.StatusTodo, false)
|
|
todoTask.TabloID = tablo.ID
|
|
|
|
inProgressTask := makeTask(taskmodel.StatusInProgress, false)
|
|
inProgressTask.TabloID = tablo.ID
|
|
|
|
doneTask := makeTask(taskmodel.StatusDone, false)
|
|
doneTask.TabloID = tablo.ID
|
|
|
|
tasks := []taskmodel.Record{todoTask, inProgressTask, doneTask}
|
|
|
|
vm := NewTabloDetailViewModel(tablo, tasks, "Test User")
|
|
|
|
if len(vm.Columns) != 4 {
|
|
t.Fatalf("expected 4 columns, got %d", len(vm.Columns))
|
|
}
|
|
|
|
columnIDs := []string{"todo", "in_progress", "in_review", "done"}
|
|
for i, col := range vm.Columns {
|
|
if col.ID != columnIDs[i] {
|
|
t.Errorf("column[%d]: expected ID %q, got %q", i, columnIDs[i], col.ID)
|
|
}
|
|
}
|
|
|
|
// Column 0 = todo — 1 task
|
|
if len(vm.Columns[0].Tasks) != 1 {
|
|
t.Errorf("todo column: expected 1 task, got %d", len(vm.Columns[0].Tasks))
|
|
}
|
|
|
|
// Column 1 = in_progress — 1 task
|
|
if len(vm.Columns[1].Tasks) != 1 {
|
|
t.Errorf("in_progress column: expected 1 task, got %d", len(vm.Columns[1].Tasks))
|
|
}
|
|
|
|
// Column 2 = in_review — 0 tasks
|
|
if len(vm.Columns[2].Tasks) != 0 {
|
|
t.Errorf("in_review column: expected 0 tasks, got %d", len(vm.Columns[2].Tasks))
|
|
}
|
|
|
|
// Column 3 = done — 1 task
|
|
if len(vm.Columns[3].Tasks) != 1 {
|
|
t.Errorf("done column: expected 1 task, got %d", len(vm.Columns[3].Tasks))
|
|
}
|
|
}
|
|
|
|
func TestNewTabloDetailViewModel_EtapesExcludedFromColumns(t *testing.T) {
|
|
tablo := makeTablo()
|
|
|
|
etapeTask := makeTask(taskmodel.StatusTodo, true)
|
|
etapeTask.TabloID = tablo.ID
|
|
|
|
regularTask := makeTask(taskmodel.StatusTodo, false)
|
|
regularTask.TabloID = tablo.ID
|
|
|
|
tasks := []taskmodel.Record{etapeTask, regularTask}
|
|
vm := NewTabloDetailViewModel(tablo, tasks, "")
|
|
|
|
// Only regular task in todo column
|
|
if len(vm.Columns[0].Tasks) != 1 {
|
|
t.Errorf("todo column: expected 1 task (etape excluded), got %d", len(vm.Columns[0].Tasks))
|
|
}
|
|
}
|
|
|
|
func TestNewTabloDetailViewModel_EtapesPopulated(t *testing.T) {
|
|
tablo := makeTablo()
|
|
|
|
etape1 := taskmodel.Record{
|
|
ID: uuid.New(),
|
|
TabloID: tablo.ID,
|
|
OwnerID: tablo.OwnerID,
|
|
Title: "Etape One",
|
|
Status: taskmodel.StatusTodo,
|
|
IsEtape: true,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
|
|
etape2 := taskmodel.Record{
|
|
ID: uuid.New(),
|
|
TabloID: tablo.ID,
|
|
OwnerID: tablo.OwnerID,
|
|
Title: "Etape Two",
|
|
Status: taskmodel.StatusTodo,
|
|
IsEtape: true,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
|
|
child1 := taskmodel.Record{
|
|
ID: uuid.New(),
|
|
TabloID: tablo.ID,
|
|
OwnerID: tablo.OwnerID,
|
|
Title: "Child A",
|
|
Status: taskmodel.StatusTodo,
|
|
IsEtape: false,
|
|
ParentTaskID: &etape1.ID,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
|
|
child2 := taskmodel.Record{
|
|
ID: uuid.New(),
|
|
TabloID: tablo.ID,
|
|
OwnerID: tablo.OwnerID,
|
|
Title: "Child B",
|
|
Status: taskmodel.StatusInProgress,
|
|
IsEtape: false,
|
|
ParentTaskID: &etape1.ID,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
|
|
orphan := taskmodel.Record{
|
|
ID: uuid.New(),
|
|
TabloID: tablo.ID,
|
|
OwnerID: tablo.OwnerID,
|
|
Title: "No Etape",
|
|
Status: taskmodel.StatusTodo,
|
|
IsEtape: false,
|
|
CreatedAt: time.Now().UTC(),
|
|
UpdatedAt: time.Now().UTC(),
|
|
}
|
|
|
|
tasks := []taskmodel.Record{etape1, etape2, child1, child2, orphan}
|
|
vm := NewTabloDetailViewModel(tablo, tasks, "")
|
|
|
|
if len(vm.Etapes) != 2 {
|
|
t.Fatalf("expected 2 etapes, got %d", len(vm.Etapes))
|
|
}
|
|
|
|
// Find etape1 in the Etapes slice
|
|
var foundEtape1 *TabloDetailEtapeView
|
|
for i := range vm.Etapes {
|
|
if vm.Etapes[i].ID == etape1.ID.String() {
|
|
foundEtape1 = &vm.Etapes[i]
|
|
break
|
|
}
|
|
}
|
|
|
|
if foundEtape1 == nil {
|
|
t.Fatal("etape1 not found in vm.Etapes")
|
|
}
|
|
|
|
if foundEtape1.TaskCount != 2 {
|
|
t.Errorf("etape1: expected TaskCount=2, got %d", foundEtape1.TaskCount)
|
|
}
|
|
|
|
// Find etape2 — should have TaskCount=0
|
|
var foundEtape2 *TabloDetailEtapeView
|
|
for i := range vm.Etapes {
|
|
if vm.Etapes[i].ID == etape2.ID.String() {
|
|
foundEtape2 = &vm.Etapes[i]
|
|
break
|
|
}
|
|
}
|
|
|
|
if foundEtape2 == nil {
|
|
t.Fatal("etape2 not found in vm.Etapes")
|
|
}
|
|
|
|
if foundEtape2.TaskCount != 0 {
|
|
t.Errorf("etape2: expected TaskCount=0, got %d", foundEtape2.TaskCount)
|
|
}
|
|
}
|