feat: move a card between statuses #3
8 changed files with 664 additions and 250 deletions
|
@ -48,12 +48,17 @@ func Open(path string) (Board, error) {
|
||||||
return board, nil
|
return board, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close closes the project's Kanban board.
|
||||||
func (b *Board) Close() error {
|
func (b *Board) Close() error {
|
||||||
if b.db == nil {
|
if b.db == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.db.Close()
|
if err := b.db.Close(); err != nil {
|
||||||
|
return fmt.Errorf("error closing the database, %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatusList returns the ordered list of statuses from the database.
|
// StatusList returns the ordered list of statuses from the database.
|
||||||
|
@ -65,18 +70,18 @@ func (b *Board) StatusList() ([]Status, error) {
|
||||||
|
|
||||||
statuses := make([]Status, len(data))
|
statuses := make([]Status, len(data))
|
||||||
|
|
||||||
for i, d := range data {
|
for ind, d := range data {
|
||||||
buf := bytes.NewBuffer(d)
|
buf := bytes.NewBuffer(d)
|
||||||
|
|
||||||
decoder := gob.NewDecoder(buf)
|
decoder := gob.NewDecoder(buf)
|
||||||
|
|
||||||
var s Status
|
var status Status
|
||||||
|
|
||||||
if err := decoder.Decode(&s); err != nil {
|
if err := decoder.Decode(&status); err != nil {
|
||||||
return []Status{}, fmt.Errorf("unable to decode data, %w", err)
|
return []Status{}, fmt.Errorf("unable to decode data, %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
statuses[i] = s
|
statuses[ind] = status
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(ByStatusOrder(statuses))
|
sort.Sort(ByStatusOrder(statuses))
|
||||||
|
@ -84,23 +89,106 @@ func (b *Board) StatusList() ([]Status, error) {
|
||||||
return statuses, nil
|
return statuses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Finish implementation.
|
// Status returns a single status from the database.
|
||||||
func (b *Board) ReadStatus() (Status, error) {
|
func (b *Board) Status(id int) (Status, error) {
|
||||||
return Status{}, nil
|
data, err := database.Read(b.db, database.StatusBucket, id)
|
||||||
|
if err != nil {
|
||||||
|
return Status{}, fmt.Errorf("unable to read status [%d] from the database, %w", id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var status Status
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(data)
|
||||||
|
|
||||||
|
decoder := gob.NewDecoder(buf)
|
||||||
|
|
||||||
|
if err := decoder.Decode(&status); err != nil {
|
||||||
|
return Status{}, fmt.Errorf("unable to decode data into a Status object, %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return status, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Finish implementation.
|
type StatusArgs struct {
|
||||||
func (b *Board) NewStatus() error {
|
Name string
|
||||||
|
Order int
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateStatus creates a status in the database.
|
||||||
|
func (b *Board) CreateStatus(args StatusArgs) error {
|
||||||
|
status := Status{
|
||||||
|
ID: -1,
|
||||||
|
Name: args.Name,
|
||||||
|
Order: args.Order,
|
||||||
|
CardIds: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := database.Write(b.db, database.StatusBucket, &status); err != nil {
|
||||||
|
return fmt.Errorf("unable to write the status to the database, %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateStatusArgs struct {
|
||||||
|
StatusID int
|
||||||
|
StatusArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStatus modifies an existing status in the database.
|
||||||
|
func (b *Board) UpdateStatus(args UpdateStatusArgs) error {
|
||||||
|
status, err := b.Status(args.StatusID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to retrieve the status from the database, %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args.Name) > 0 {
|
||||||
|
status.Name = args.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.Order > 0 {
|
||||||
|
status.Order = args.Order
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := database.Write(b.db, database.StatusBucket, &status); err != nil {
|
||||||
|
return fmt.Errorf("unable to write the status to the database, %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Finish implementation.
|
// TODO: Finish implementation.
|
||||||
func (b *Board) UpdateStatus() error {
|
// func (b *Board) DeleteStatus() error {
|
||||||
return nil
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
type MoveToStatusArgs struct {
|
||||||
|
CardID int
|
||||||
|
CurrentStatusID int
|
||||||
|
NextStatusID int
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Finish implementation.
|
// MoveToStatus moves a card between statuses.
|
||||||
func (b *Board) DeleteStatus() error {
|
func (b *Board) MoveToStatus(args MoveToStatusArgs) error {
|
||||||
|
currentStatus, err := b.Status(args.CurrentStatusID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to get the card's current status [%d], %w", args.CurrentStatusID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextStatus, err := b.Status(args.NextStatusID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to get the card's next status [%d], %w", args.NextStatusID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextStatus.AddCardID(args.CardID)
|
||||||
|
currentStatus.RemoveCardID(args.CardID)
|
||||||
|
|
||||||
|
boltItems := []database.BoltItem{¤tStatus, &nextStatus}
|
||||||
|
|
||||||
|
if _, err := database.WriteMany(b.db, database.StatusBucket, boltItems); err != nil {
|
||||||
|
return fmt.Errorf("unable to update the statuses in the database, %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,14 +198,14 @@ type CardArgs struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateCard creates a card in the database.
|
// CreateCard creates a card in the database.
|
||||||
func (b *Board) CreateCard(args CardArgs) error {
|
func (b *Board) CreateCard(args CardArgs) (int, error) {
|
||||||
statusList, err := b.StatusList()
|
statusList, err := b.StatusList()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to read the status list, %w", err)
|
return 0, fmt.Errorf("unable to read the status list, %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(statusList) == 0 {
|
if len(statusList) == 0 {
|
||||||
return statusListEmptyError{}
|
return 0, statusListEmptyError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
card := Card{
|
card := Card{
|
||||||
|
@ -128,18 +216,19 @@ func (b *Board) CreateCard(args CardArgs) error {
|
||||||
|
|
||||||
cardID, err := database.Write(b.db, database.CardBucket, &card)
|
cardID, err := database.Write(b.db, database.CardBucket, &card)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to write card to the database, %w", err)
|
return 0, fmt.Errorf("unable to write card to the database, %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
initialStatus := statusList[0]
|
initialStatus := statusList[0]
|
||||||
|
|
||||||
initialStatus.AddCardID(cardID)
|
initialStatus.AddCardID(cardID)
|
||||||
|
|
||||||
if _, err := database.Write(b.db, database.StatusBucket, &initialStatus); err != nil {
|
id, err := database.Write(b.db, database.StatusBucket, &initialStatus)
|
||||||
return fmt.Errorf("unable to write the %s status to the database, %w", initialStatus.Name, err)
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("unable to write the %s status to the database, %w", initialStatus.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Card returns a Card value from the database.
|
// Card returns a Card value from the database.
|
||||||
|
@ -172,18 +261,18 @@ func (b *Board) CardList(ids []int) ([]Card, error) {
|
||||||
|
|
||||||
cards := make([]Card, len(data))
|
cards := make([]Card, len(data))
|
||||||
|
|
||||||
for i, d := range data {
|
for ind, d := range data {
|
||||||
buf := bytes.NewBuffer(d)
|
buf := bytes.NewBuffer(d)
|
||||||
|
|
||||||
decoder := gob.NewDecoder(buf)
|
decoder := gob.NewDecoder(buf)
|
||||||
|
|
||||||
var c Card
|
var card Card
|
||||||
|
|
||||||
if err := decoder.Decode(&c); err != nil {
|
if err := decoder.Decode(&card); err != nil {
|
||||||
return nil, fmt.Errorf("unable to decode data, %w", err)
|
return nil, fmt.Errorf("unable to decode data, %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cards[i] = c
|
cards[ind] = card
|
||||||
}
|
}
|
||||||
|
|
||||||
return cards, nil
|
return cards, nil
|
||||||
|
@ -216,20 +305,8 @@ func (b *Board) UpdateCard(args UpdateCardArgs) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateCardStatusArgs struct {
|
|
||||||
CardID int
|
|
||||||
OldStatusID int
|
|
||||||
NewStatusID int
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateCardStatus moves a card between statuses.
|
|
||||||
// TODO: finish implementation.
|
|
||||||
func (b *Board) UpdateCardStatus(args UpdateCardStatusArgs) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteCard deletes a card from the database.
|
// DeleteCard deletes a card from the database.
|
||||||
// TODO: finish implementation.
|
// TODO: finish implementation.
|
||||||
func (b *Board) DeleteCard(id int) error {
|
//func (b *Board) DeleteCard(id int) error {
|
||||||
return nil
|
// return nil
|
||||||
}
|
//}
|
||||||
|
|
|
@ -1,178 +0,0 @@
|
||||||
package board_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"codeflow.dananglin.me.uk/apollo/canal/internal/board"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCardLifecycle(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
projectDir, err := projectRoot()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
testDBPath := filepath.Join(projectDir, "test", "databases", "Board_TestCardLifecycle.db")
|
|
||||||
os.Remove(testDBPath)
|
|
||||||
|
|
||||||
b, err := board.Open(testDBPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to open the test database %s, %s.", testDBPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
_ = b.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
initialCardTitle := "A test card."
|
|
||||||
initialCardContent := "Ensure that this card is safely stored in the database."
|
|
||||||
expectedCardID := 1
|
|
||||||
|
|
||||||
testCreateCard(t, b, initialCardTitle, initialCardContent, expectedCardID)
|
|
||||||
testReadCard(t, b, expectedCardID, initialCardTitle, initialCardContent)
|
|
||||||
|
|
||||||
modifiedCardTitle := "Test card updated."
|
|
||||||
modifiedCardContent1 := "Ensure that this card is safely updated in the database."
|
|
||||||
|
|
||||||
testUpdateCard(t, b, expectedCardID, modifiedCardTitle, modifiedCardContent1)
|
|
||||||
|
|
||||||
modifiedCardContent2 := "Updated card content only."
|
|
||||||
|
|
||||||
testUpdateCardContent(t, b, expectedCardID, modifiedCardTitle, modifiedCardContent2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testCreateCard(t *testing.T, b board.Board, title, content string, wantID int) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
args := board.CardArgs{
|
|
||||||
NewTitle: title,
|
|
||||||
NewContent: content,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.CreateCard(args); err != nil {
|
|
||||||
t.Fatalf("Unable to create the test card, %s.", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
statusList, err := b.StatusList()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to run `ReadStatusList`, %s.", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(statusList) == 0 {
|
|
||||||
t.Fatal("The status list appears to be empty.")
|
|
||||||
}
|
|
||||||
|
|
||||||
cardIDs := statusList[0].CardIds
|
|
||||||
|
|
||||||
if len(cardIDs) != 1 {
|
|
||||||
t.Fatalf("Unexpected number of cards in the default status, want: %d, got %d.", 1, len(cardIDs))
|
|
||||||
}
|
|
||||||
|
|
||||||
if gotID := cardIDs[0]; wantID != gotID {
|
|
||||||
t.Errorf("Unexpected card ID found in the default status, want: %d, got %d.", wantID, gotID)
|
|
||||||
} else {
|
|
||||||
t.Logf("Expected card ID found in the default status, got %d.", gotID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testReadCard(t *testing.T, b board.Board, cardID int, wantTitle, wantContent string) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
card, err := b.Card(cardID)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to read test card, %s.", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if card.Title != wantTitle {
|
|
||||||
t.Errorf("Unexpected card title received, want: %s, got: %s.", wantTitle, card.Title)
|
|
||||||
} else {
|
|
||||||
t.Logf("Expected card title received, got: %s.", card.Title)
|
|
||||||
}
|
|
||||||
|
|
||||||
if card.Content != wantContent {
|
|
||||||
t.Errorf("Unexpected card content received, want: %s, got: %s.", wantContent, card.Content)
|
|
||||||
} else {
|
|
||||||
t.Logf("Expected card title received, got: %s.", card.Content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testUpdateCard(t *testing.T, b board.Board, cardID int, newTitle, newContent string) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
args := board.UpdateCardArgs{
|
|
||||||
CardID: cardID,
|
|
||||||
CardArgs: board.CardArgs{
|
|
||||||
NewTitle: newTitle,
|
|
||||||
NewContent: newContent,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.UpdateCard(args); err != nil {
|
|
||||||
t.Fatalf("Unable to update the test card, %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
got, err := b.Card(cardID)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to read the modified test card, %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
want := board.Card{
|
|
||||||
ID: cardID,
|
|
||||||
Title: newTitle,
|
|
||||||
Content: newContent,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(got, want) {
|
|
||||||
t.Errorf("Unexpected card read from the database: want %+v, got %+v", want, got)
|
|
||||||
} else {
|
|
||||||
t.Logf("Expected card read from the database: got %+v", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testUpdateCardContent(t *testing.T, b board.Board, cardID int, expectedTitle, newContent string) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
args := board.UpdateCardArgs{
|
|
||||||
CardID: cardID,
|
|
||||||
CardArgs: board.CardArgs{
|
|
||||||
NewTitle: "",
|
|
||||||
NewContent: newContent,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.UpdateCard(args); err != nil {
|
|
||||||
t.Fatalf("Unable to update the test card, %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
got, err := b.Card(cardID)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to read the modified test card, %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
want := board.Card{
|
|
||||||
ID: cardID,
|
|
||||||
Title: expectedTitle,
|
|
||||||
Content: newContent,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(got, want) {
|
|
||||||
t.Errorf("Unexpected card read from the database, want: %+v, got: %+v", want, got)
|
|
||||||
} else {
|
|
||||||
t.Logf("Expected card read from the database, got: %+v", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func projectRoot() (string, error) {
|
|
||||||
cwd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("unable to get the current working directory, %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.Join(cwd, "..", ".."), nil
|
|
||||||
}
|
|
177
internal/board/card_lifecycle_test.go
Normal file
177
internal/board/card_lifecycle_test.go
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
package board_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"codeflow.dananglin.me.uk/apollo/canal/internal/board"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCardLifecycle(t *testing.T) {
|
||||||
|
t.Log("Testing the lifecycle of a card.")
|
||||||
|
|
||||||
|
projectDir, err := projectRoot()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
testDBPath := filepath.Join(projectDir, "test", "databases", "Board_TestCardLifecycle.db")
|
||||||
|
os.Remove(testDBPath)
|
||||||
|
|
||||||
|
b, err := board.Open(testDBPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to open the test Kanban board, %s.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = b.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
initialCardTitle := "A test card."
|
||||||
|
initialCardContent := "Ensure that this card is safely stored in the database."
|
||||||
|
expectedCardID := 1
|
||||||
|
|
||||||
|
t.Run("Test Create Card", testCreateCard(b, initialCardTitle, initialCardContent, expectedCardID))
|
||||||
|
|
||||||
|
t.Run("Test Read Card", testReadCard(b, expectedCardID, initialCardTitle, initialCardContent))
|
||||||
|
|
||||||
|
modifiedCardTitle := "Test card updated."
|
||||||
|
modifiedCardContent1 := "Ensure that this card is safely updated in the database."
|
||||||
|
|
||||||
|
t.Run("Test Update Card", testUpdateCard(b, expectedCardID, modifiedCardTitle, modifiedCardContent1))
|
||||||
|
|
||||||
|
modifiedCardContent2 := "Updated card content only."
|
||||||
|
|
||||||
|
t.Run("Test Update Card Content", testUpdateCardContent(b, expectedCardID, modifiedCardTitle, modifiedCardContent2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCreateCard(b board.Board, title, content string, wantID int) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
t.Log("When the card is created and saved to the database.")
|
||||||
|
|
||||||
|
args := board.CardArgs{
|
||||||
|
NewTitle: title,
|
||||||
|
NewContent: content,
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := b.CreateCard(args); err != nil {
|
||||||
|
t.Fatalf("ERROR: Unable to create the test card, %s.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
statusList, err := b.StatusList()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ERROR: Unable to run `ReadStatusList`, %s.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(statusList) == 0 {
|
||||||
|
t.Fatal("ERROR: The status list appears to be empty.")
|
||||||
|
}
|
||||||
|
|
||||||
|
cardIDs := statusList[0].CardIds
|
||||||
|
|
||||||
|
if len(cardIDs) != 1 {
|
||||||
|
t.Fatalf("ERROR: Unexpected number of cards in the default status, want: %d, got %d.", 1, len(cardIDs))
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotID := cardIDs[0]; wantID != gotID {
|
||||||
|
t.Errorf("%s\tUnexpected card ID found in the default status, want: %d, got %d.", failure, wantID, gotID)
|
||||||
|
} else {
|
||||||
|
t.Logf("%s\tExpected card ID found in the default status, got %d.", success, gotID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testReadCard(b board.Board, cardID int, wantTitle, wantContent string) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
t.Log("When a card is read from the database.")
|
||||||
|
|
||||||
|
card, err := b.Card(cardID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ERROR: Unable to read test card, %s.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if card.Title != wantTitle {
|
||||||
|
t.Errorf("%s\tUnexpected card title received, want: %s, got: %s.", failure, wantTitle, card.Title)
|
||||||
|
} else {
|
||||||
|
t.Logf("%s\tExpected card title received, got: %s.", success, card.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
if card.Content != wantContent {
|
||||||
|
t.Errorf("%s\tUnexpected card content received, want: %s, got: %s.", failure, wantContent, card.Content)
|
||||||
|
} else {
|
||||||
|
t.Logf("%s\tExpected card content received, got: %s.", success, card.Content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUpdateCard(b board.Board, cardID int, newTitle, newContent string) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
t.Log("When a card is updated in the database.")
|
||||||
|
|
||||||
|
args := board.UpdateCardArgs{
|
||||||
|
CardID: cardID,
|
||||||
|
CardArgs: board.CardArgs{
|
||||||
|
NewTitle: newTitle,
|
||||||
|
NewContent: newContent,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.UpdateCard(args); err != nil {
|
||||||
|
t.Fatalf("ERROR: Unable to update the test card, %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := b.Card(cardID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ERROR: Unable to read the modified test card, %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := board.Card{
|
||||||
|
ID: cardID,
|
||||||
|
Title: newTitle,
|
||||||
|
Content: newContent,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("%s\tUnexpected card read from the database: want %+v, got %+v", failure, want, got)
|
||||||
|
} else {
|
||||||
|
t.Logf("%s\tExpected card read from the database: got %+v", success, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUpdateCardContent(b board.Board, cardID int, expectedTitle, newContent string) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
t.Log("When (and only when) a card's content is updated in the database.")
|
||||||
|
|
||||||
|
args := board.UpdateCardArgs{
|
||||||
|
CardID: cardID,
|
||||||
|
CardArgs: board.CardArgs{
|
||||||
|
NewTitle: "",
|
||||||
|
NewContent: newContent,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.UpdateCard(args); err != nil {
|
||||||
|
t.Fatalf("ERROR: Unable to update the test card, %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := b.Card(cardID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ERROR: Unable to read the modified test card, %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := board.Card{
|
||||||
|
ID: cardID,
|
||||||
|
Title: expectedTitle,
|
||||||
|
Content: newContent,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("%s\tUnexpected card read from the database, want: %+v, got: %+v", failure, want, got)
|
||||||
|
} else {
|
||||||
|
t.Logf("%s\tExpected card read from the database, got: %+v", success, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
internal/board/helpers_test.go
Normal file
21
internal/board/helpers_test.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package board_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
success = "\u2713"
|
||||||
|
failure = "\u2717"
|
||||||
|
)
|
||||||
|
|
||||||
|
func projectRoot() (string, error) {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("unable to get the current working directory, %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(cwd, "..", ".."), nil
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
package board
|
package board
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
// Status represents the status of the Kanban board.
|
// Status represents the status of the Kanban board.
|
||||||
type Status struct {
|
type Status struct {
|
||||||
ID int
|
ID int
|
||||||
|
@ -18,8 +22,61 @@ func (s *Status) Id() int {
|
||||||
return s.ID
|
return s.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Status) AddCardID(id int) {
|
// AddCardID adds a card ID to the status' list of card IDs.
|
||||||
s.CardIds = append(s.CardIds, id)
|
func (s *Status) AddCardID(cardID int) {
|
||||||
|
// Create a new list if it does not exist
|
||||||
|
// and then return.
|
||||||
|
if s.CardIds == nil {
|
||||||
|
s.CardIds = []int{cardID}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort list if not sorted.
|
||||||
|
if !sort.IntsAreSorted(s.CardIds) {
|
||||||
|
sort.Ints(s.CardIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get index of the card's ID if it already exists in the list.
|
||||||
|
// Return if it already exists in the list
|
||||||
|
ind := sort.SearchInts(s.CardIds, cardID)
|
||||||
|
|
||||||
|
if ind <= len(s.CardIds) && cardID == s.CardIds[ind] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.CardIds = append(s.CardIds, cardID)
|
||||||
|
sort.Ints(s.CardIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveCardID removes a card ID from the status' list of card IDs.
|
||||||
|
func (s *Status) RemoveCardID(cardID int) {
|
||||||
|
if s.CardIds == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort list if not sorted.
|
||||||
|
if !sort.IntsAreSorted(s.CardIds) {
|
||||||
|
sort.Ints(s.CardIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get index of id.
|
||||||
|
// If the card ID is somehow not in the list, then ind
|
||||||
|
// will be the index where the id can be inserted.
|
||||||
|
ind := sort.SearchInts(s.CardIds, cardID)
|
||||||
|
|
||||||
|
if ind >= len(s.CardIds) || cardID != s.CardIds[ind] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.CardIds) == 1 {
|
||||||
|
s.CardIds = nil
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// use append to eliminate the id from the new slice
|
||||||
|
s.CardIds = append(s.CardIds[:ind], s.CardIds[ind+1:]...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ByStatusOrder implements sort.Interface for []Status based on the Order field.
|
// ByStatusOrder implements sort.Interface for []Status based on the Order field.
|
||||||
|
@ -41,19 +98,22 @@ func (s ByStatusOrder) Less(i, j int) bool {
|
||||||
func defaultStatusList() []Status {
|
func defaultStatusList() []Status {
|
||||||
return []Status{
|
return []Status{
|
||||||
{
|
{
|
||||||
ID: -1,
|
ID: -1,
|
||||||
Name: "To Do",
|
Name: "To Do",
|
||||||
Order: 1,
|
Order: 1,
|
||||||
|
CardIds: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: -1,
|
ID: -1,
|
||||||
Name: "Doing",
|
Name: "Doing",
|
||||||
Order: 2,
|
Order: 2,
|
||||||
|
CardIds: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: -1,
|
ID: -1,
|
||||||
Name: "Done",
|
Name: "Done",
|
||||||
Order: 3,
|
Order: 3,
|
||||||
|
CardIds: nil,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
192
internal/board/status_lifecycle_test.go
Normal file
192
internal/board/status_lifecycle_test.go
Normal file
|
@ -0,0 +1,192 @@
|
||||||
|
package board_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"codeflow.dananglin.me.uk/apollo/canal/internal/board"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStatusLifecycle(t *testing.T) {
|
||||||
|
t.Log("Testing the lifecycle of a status.")
|
||||||
|
|
||||||
|
projectDir, err := projectRoot()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
testDBPath := filepath.Join(projectDir, "test", "databases", "Board_TestStatusLifecycle.db")
|
||||||
|
os.Remove(testDBPath)
|
||||||
|
|
||||||
|
kanban, err := board.Open(testDBPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to open the test Kanban board, %s.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = kanban.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// We've opened a new board with the default list of statuses: To Do, Doing, Done
|
||||||
|
statusOnHoldName := "On Hold"
|
||||||
|
statusOnHoldExpectedID := 4
|
||||||
|
statusOnHoldOrder := 4
|
||||||
|
|
||||||
|
t.Run("Test Create Status", testCreateStatus(kanban, statusOnHoldName, statusOnHoldOrder))
|
||||||
|
|
||||||
|
t.Run("Test Read Status", testReadStatus(kanban, statusOnHoldExpectedID, statusOnHoldName))
|
||||||
|
|
||||||
|
t.Run("Test Status Update", testUpdateStatus(kanban, 2, "In Progress"))
|
||||||
|
// Rearrange the board so the order is To Do, On Hold, In Progress, Done
|
||||||
|
// Move a Card ID from To Do to In Progress
|
||||||
|
t.Run("Test Move Card To Status", testMoveCardToStatus(kanban))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCreateStatus(b board.Board, name string, order int) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
t.Log("When the status is created and saved to the database.")
|
||||||
|
|
||||||
|
args := board.StatusArgs{
|
||||||
|
Name: name,
|
||||||
|
Order: order,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.CreateStatus(args); err != nil {
|
||||||
|
t.Fatalf("ERROR: Unable to create the new status, %v.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("%s\tStatus '%s' was successfully saved to the database.", success, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testReadStatus(b board.Board, statusID int, wantName string) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
t.Log("When the status is read from the database.")
|
||||||
|
|
||||||
|
status, err := b.Status(statusID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ERROR: Unable to read the test status, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if status.Name != wantName {
|
||||||
|
t.Errorf("%s\tUnexpected status received from the database, want: '%s', got: '%s'", failure, wantName, status.Name)
|
||||||
|
} else {
|
||||||
|
t.Logf("%s\tSuccessfully received the '%s' status from the database.", success, status.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUpdateStatus(kanban board.Board, statusID int, newName string) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
t.Log("When the status' name is updated in the database.")
|
||||||
|
|
||||||
|
args := board.UpdateStatusArgs{
|
||||||
|
StatusID: statusID,
|
||||||
|
StatusArgs: board.StatusArgs{
|
||||||
|
Name: newName,
|
||||||
|
Order: -1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := kanban.UpdateStatus(args); err != nil {
|
||||||
|
t.Fatalf("ERROR: Unable to update the status, %v", err)
|
||||||
|
} else {
|
||||||
|
t.Logf("%s\tStatus successfully updated.", success)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log("Verifying the new status.")
|
||||||
|
|
||||||
|
status, err := kanban.Status(statusID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ERROR: Unable to retrieve the status from the database, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
want := board.Status{
|
||||||
|
ID: statusID,
|
||||||
|
Name: newName,
|
||||||
|
CardIds: nil,
|
||||||
|
Order: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(status, want) {
|
||||||
|
t.Errorf("%s\tUnexpected status received from the database, want: %+v, got: %+v", failure, want, status)
|
||||||
|
} else {
|
||||||
|
t.Logf("%s\tExpected status name received from the database, got: %+v", success, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// func testRearrangeBoard() func(t *testing.T) {
|
||||||
|
// return func(t *testing.T) {
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
func testMoveCardToStatus(b board.Board) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
t.Log("When moving a card between statuses.")
|
||||||
|
|
||||||
|
title := "Test card."
|
||||||
|
|
||||||
|
cardArgs := board.CardArgs{
|
||||||
|
NewTitle: title,
|
||||||
|
NewContent: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
cardID, err := b.CreateCard(cardArgs)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ERROR: Unable to create the card in the database, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
statusList, err := b.StatusList()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ERROR: Unable to retrieve the list of statuses from the database, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
status0 := statusList[0]
|
||||||
|
status2 := statusList[2]
|
||||||
|
|
||||||
|
moveArgs := board.MoveToStatusArgs{
|
||||||
|
CardID: cardID,
|
||||||
|
CurrentStatusID: status0.ID,
|
||||||
|
NextStatusID: status2.ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.MoveToStatus(moveArgs); err != nil {
|
||||||
|
t.Fatalf("ERROR: Unable to move the Card ID from '%s' to '%s', %v", status0.Name, status2.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Verifying that the card has moved to '%s'", status2.Name)
|
||||||
|
|
||||||
|
statusList, err = b.StatusList()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ERROR: Unable to retrieve the list of statuses from the database, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
status0 = statusList[0]
|
||||||
|
status2 = statusList[2]
|
||||||
|
|
||||||
|
if len(status0.CardIds) != 0 {
|
||||||
|
t.Errorf("%s\tUnexpected number of card IDs found in '%s', want: 0, got: %d", failure, status0.Name, len(status0.CardIds))
|
||||||
|
} else {
|
||||||
|
t.Logf("%s\tThe number of card IDs in '%s' is now %d", success, status0.Name, len(status0.CardIds))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(status2.CardIds) != 1 {
|
||||||
|
t.Errorf("%s\tUnexpected number of card IDs found in '%s', want: 1, got: %d", failure, status2.Name, len(status2.CardIds))
|
||||||
|
} else {
|
||||||
|
t.Logf("%s\tThe number of card IDs in '%s' is now %d", success, status2.Name, len(status2.CardIds))
|
||||||
|
}
|
||||||
|
|
||||||
|
card, err := b.Card(status2.CardIds[0])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ERROR: Unable to retrieve the card from the database, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if card.Title != title {
|
||||||
|
t.Errorf("%s\tUnexpected card title found in '%s', want: '%s', got: '%s'", success, status2.Name, title, card.Title)
|
||||||
|
} else {
|
||||||
|
t.Logf("%s\tExpected card title found in '%s', got: '%s'", success, status2.Name, card.Title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,17 +2,16 @@ package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"codeflow.dananglin.me.uk/apollo/canal/internal/board"
|
"codeflow.dananglin.me.uk/apollo/canal/internal/board"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/rivo/tview"
|
"github.com/rivo/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
type column struct {
|
type column struct {
|
||||||
statusID int
|
statusID int
|
||||||
statusName string
|
cards *tview.List
|
||||||
cards *tview.List
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UI) newColumn(status board.Status) (column, error) {
|
func (u *UI) newColumn(status board.Status) (column, error) {
|
||||||
|
@ -49,28 +48,31 @@ func (u *UI) newColumn(status board.Status) (column, error) {
|
||||||
cur := cardList.GetCurrentItem()
|
cur := cardList.GetCurrentItem()
|
||||||
cur--
|
cur--
|
||||||
cardList.SetCurrentItem(cur)
|
cardList.SetCurrentItem(cur)
|
||||||
|
case 'm':
|
||||||
|
u.pages.ShowPage(movePageName)
|
||||||
|
u.SetFocus(u.move)
|
||||||
}
|
}
|
||||||
|
|
||||||
return event
|
return event
|
||||||
})
|
})
|
||||||
|
|
||||||
if len(status.CardIds) > 0 {
|
if status.CardIds != nil && len(status.CardIds) > 0 {
|
||||||
cards, err := u.board.CardList(status.CardIds)
|
cards, err := u.board.CardList(status.CardIds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return column{}, fmt.Errorf("unable to get the card list. %w", err)
|
return column{}, fmt.Errorf("unable to get the card list. %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cards {
|
for _, c := range cards {
|
||||||
cardList.AddItem(fmt.Sprintf("[%d] %s", c.Id(), c.Title), "", 0, nil)
|
id := strconv.Itoa(c.ID)
|
||||||
|
cardList.AddItem(fmt.Sprintf("[%s] %s", id, c.Title), id, 0, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
u.flex.AddItem(cardList, 0, 1, false)
|
u.flex.AddItem(cardList, 0, 1, false)
|
||||||
|
|
||||||
c := column{
|
c := column{
|
||||||
statusID: status.ID,
|
statusID: status.ID,
|
||||||
statusName: status.Name,
|
cards: cardList,
|
||||||
cards: cardList,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
|
@ -99,7 +101,7 @@ func (u *UI) shiftColumnFocus(s int) {
|
||||||
u.setColumnFocus()
|
u.setColumnFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UI) updateColumns(statusList []board.Status) error {
|
func (u *UI) updateColumns(statusList []board.Status) {
|
||||||
u.flex.Clear()
|
u.flex.Clear()
|
||||||
|
|
||||||
columns := make([]column, len(statusList))
|
columns := make([]column, len(statusList))
|
||||||
|
@ -109,6 +111,4 @@ func (u *UI) updateColumns(statusList []board.Status) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
u.columns = columns
|
u.columns = columns
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"codeflow.dananglin.me.uk/apollo/canal/internal/board"
|
"codeflow.dananglin.me.uk/apollo/canal/internal/board"
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
|
@ -17,6 +18,7 @@ const (
|
||||||
mainPageName string = "main"
|
mainPageName string = "main"
|
||||||
quitPageName string = "quit"
|
quitPageName string = "quit"
|
||||||
addPageName string = "add"
|
addPageName string = "add"
|
||||||
|
movePageName string = "move"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UI struct {
|
type UI struct {
|
||||||
|
@ -29,22 +31,26 @@ type UI struct {
|
||||||
board board.Board
|
board board.Board
|
||||||
quit *tview.Modal
|
quit *tview.Modal
|
||||||
add *modalInput
|
add *modalInput
|
||||||
|
move *tview.Flex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUI returns a new UI value.
|
// NewUI returns a new UI value.
|
||||||
func NewUI() UI {
|
func NewUI() UI {
|
||||||
u := UI{
|
ui := UI{
|
||||||
Application: tview.NewApplication(),
|
Application: tview.NewApplication(),
|
||||||
pages: tview.NewPages(),
|
pages: tview.NewPages(),
|
||||||
flex: tview.NewFlex(),
|
flex: tview.NewFlex(),
|
||||||
quit: tview.NewModal(),
|
quit: tview.NewModal(),
|
||||||
add: NewModalInput(),
|
add: NewModalInput(),
|
||||||
focusedColumn: 0,
|
focusedColumn: 0,
|
||||||
|
columns: nil,
|
||||||
|
move: nil,
|
||||||
|
board: board.Board{},
|
||||||
}
|
}
|
||||||
|
|
||||||
u.init()
|
ui.init()
|
||||||
|
|
||||||
return u
|
return ui
|
||||||
}
|
}
|
||||||
|
|
||||||
// closeBoard closes the board.
|
// closeBoard closes the board.
|
||||||
|
@ -66,7 +72,7 @@ func (u *UI) init() {
|
||||||
switch event.Rune() {
|
switch event.Rune() {
|
||||||
case 'o':
|
case 'o':
|
||||||
if u.flex.HasFocus() && len(u.columns) == 0 {
|
if u.flex.HasFocus() && len(u.columns) == 0 {
|
||||||
u.openBoard("")
|
_ = u.openBoard("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,6 +88,7 @@ func (u *UI) initAddInputModal() {
|
||||||
if success {
|
if success {
|
||||||
_ = u.newCard(text, "")
|
_ = u.newCard(text, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
u.pages.HidePage(addPageName)
|
u.pages.HidePage(addPageName)
|
||||||
u.setColumnFocus()
|
u.setColumnFocus()
|
||||||
}
|
}
|
||||||
|
@ -113,11 +120,11 @@ func (u *UI) newCard(title, content string) error {
|
||||||
NewContent: content,
|
NewContent: content,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := u.board.CreateCard(args); err != nil {
|
if _, err := u.board.CreateCard(args); err != nil {
|
||||||
return fmt.Errorf("unable to create card, %w", err)
|
return fmt.Errorf("unable to create card, %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
u.refresh()
|
_ = u.refresh()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -132,7 +139,7 @@ func (u *UI) openBoard(path string) error {
|
||||||
u.board = b
|
u.board = b
|
||||||
|
|
||||||
if err = u.refresh(); err != nil {
|
if err = u.refresh(); err != nil {
|
||||||
return err
|
return fmt.Errorf("error refreshing the board, %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -147,9 +154,9 @@ func (u *UI) refresh() error {
|
||||||
|
|
||||||
u.updateColumns(statusList)
|
u.updateColumns(statusList)
|
||||||
|
|
||||||
u.setColumnFocus()
|
u.updateMovePage(statusList)
|
||||||
|
|
||||||
// TODO: update move status page here
|
u.setColumnFocus()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -159,3 +166,61 @@ func (u *UI) shutdown() {
|
||||||
u.closeBoard()
|
u.closeBoard()
|
||||||
u.Stop()
|
u.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *UI) updateMovePage(statusList []board.Status) {
|
||||||
|
if u.pages.HasPage(movePageName) {
|
||||||
|
u.pages.RemovePage(movePageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
move := tview.NewFlex()
|
||||||
|
|
||||||
|
statusSelection := tview.NewList()
|
||||||
|
statusSelection.SetBorder(true)
|
||||||
|
statusSelection.ShowSecondaryText(false)
|
||||||
|
statusSelection.SetHighlightFullLine(true)
|
||||||
|
statusSelection.SetSelectedFocusOnly(true)
|
||||||
|
statusSelection.SetWrapAround(false)
|
||||||
|
|
||||||
|
doneFunc := func() {
|
||||||
|
u.pages.HidePage(movePageName)
|
||||||
|
u.setColumnFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
statusSelection.SetDoneFunc(doneFunc)
|
||||||
|
|
||||||
|
selectedFunc := func(_ int, _, secondary string, _ rune) {
|
||||||
|
currentStatusID := u.columns[u.focusedColumn].statusID
|
||||||
|
|
||||||
|
nextStatusID, err := strconv.Atoi(secondary)
|
||||||
|
if err != nil {
|
||||||
|
nextStatusID = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
currentItem := u.columns[u.focusedColumn].cards.GetCurrentItem()
|
||||||
|
_, cardIDText := u.columns[u.focusedColumn].cards.GetItemText(currentItem)
|
||||||
|
cardID, _ := strconv.Atoi(cardIDText)
|
||||||
|
|
||||||
|
args := board.MoveToStatusArgs{
|
||||||
|
CardID: cardID,
|
||||||
|
CurrentStatusID: currentStatusID,
|
||||||
|
NextStatusID: nextStatusID,
|
||||||
|
}
|
||||||
|
_ = u.board.MoveToStatus(args)
|
||||||
|
|
||||||
|
u.pages.HidePage(movePageName)
|
||||||
|
_ = u.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
statusSelection.SetSelectedFunc(selectedFunc)
|
||||||
|
|
||||||
|
for _, status := range statusList {
|
||||||
|
id := strconv.Itoa(status.ID)
|
||||||
|
statusSelection.AddItem(fmt.Sprintf("\u25C9 %s", status.Name), id, 0, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
move.AddItem(statusSelection, 0, 1, true)
|
||||||
|
|
||||||
|
u.move = move
|
||||||
|
|
||||||
|
u.pages.AddPage(movePageName, move, false, false)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue