diff --git a/examples/gno.land/p/demo/todolist/gno.mod b/examples/gno.land/p/demo/todolist/gno.mod new file mode 100644 index 00000000000..a51528b9500 --- /dev/null +++ b/examples/gno.land/p/demo/todolist/gno.mod @@ -0,0 +1,3 @@ +module gno.land/p/demo/todolist + +require gno.land/p/demo/avl v0.0.0-latest diff --git a/examples/gno.land/p/demo/todolist/todolist.gno b/examples/gno.land/p/demo/todolist/todolist.gno new file mode 100644 index 00000000000..a675344655f --- /dev/null +++ b/examples/gno.land/p/demo/todolist/todolist.gno @@ -0,0 +1,63 @@ +package todolist + +import ( + "std" + "strconv" + + "gno.land/p/demo/avl" +) + +type TodoList struct { + Title string + Tasks *avl.Tree + Owner std.Address +} + +type Task struct { + Title string + Done bool +} + +func NewTodoList(title string) *TodoList { + return &TodoList{ + Title: title, + Tasks: avl.NewTree(), + Owner: std.GetOrigCaller(), + } +} + +func NewTask(title string) *Task { + return &Task{ + Title: title, + Done: false, + } +} + +func (tl *TodoList) AddTask(id int, task *Task) { + tl.Tasks.Set(strconv.Itoa(id), task) +} + +func ToggleTaskStatus(task *Task) { + task.Done = !task.Done +} + +func (tl *TodoList) RemoveTask(taskId string) { + tl.Tasks.Remove(taskId) +} + +func (tl *TodoList) GetTasks() []*Task { + tasks := make([]*Task, 0, tl.Tasks.Size()) + tl.Tasks.Iterate("", "", func(key string, value interface{}) bool { + tasks = append(tasks, value.(*Task)) + return false + }) + return tasks +} + +func (tl *TodoList) GetTodolistOwner() std.Address { + return tl.Owner +} + +func (tl *TodoList) GetTodolistTitle() string { + return tl.Title +} diff --git a/examples/gno.land/p/demo/todolist/todolist_test.gno b/examples/gno.land/p/demo/todolist/todolist_test.gno new file mode 100644 index 00000000000..5b2bb361881 --- /dev/null +++ b/examples/gno.land/p/demo/todolist/todolist_test.gno @@ -0,0 +1,81 @@ +package todolist + +import ( + "std" + "testing" +) + +func TestNewTodoList(t *testing.T) { + title := "My Todo List" + todoList := NewTodoList(title) + + if todoList.GetTodolistTitle() != title { + t.Errorf("Expected title %q, got %q", title, todoList.GetTodolistTitle()) + } + + if len(todoList.GetTasks()) != 0 { + t.Errorf("Expected 0 tasks, got %d", len(todoList.GetTasks())) + } + + if todoList.GetTodolistOwner() != std.GetOrigCaller() { + t.Errorf("Expected owner %v, got %v", std.GetOrigCaller(), todoList.GetTodolistOwner()) + } +} + +func TestNewTask(t *testing.T) { + title := "My Task" + task := NewTask(title) + + if task.Title != title { + t.Errorf("Expected title %q, got %q", title, task.Title) + } + + if task.Done { + t.Errorf("Expected task to be not done, but it is done") + } +} + +func TestAddTask(t *testing.T) { + todoList := NewTodoList("My Todo List") + task := NewTask("My Task") + + todoList.AddTask(1, task) + + tasks := todoList.GetTasks() + if len(tasks) != 1 { + t.Errorf("Expected 1 task, got %d", len(tasks)) + } + + if tasks[0] != task { + t.Errorf("Expected task %v, got %v", task, tasks[0]) + } +} + +func TestToggleTaskStatus(t *testing.T) { + task := NewTask("My Task") + + ToggleTaskStatus(task) + + if !task.Done { + t.Errorf("Expected task to be done, but it is not done") + } + + ToggleTaskStatus(task) + + if task.Done { + t.Errorf("Expected task to be not done, but it is done") + } +} + +func TestRemoveTask(t *testing.T) { + todoList := NewTodoList("My Todo List") + task := NewTask("My Task") + todoList.AddTask(1, task) + + todoList.RemoveTask("1") + + tasks := todoList.GetTasks() + if len(tasks) != 0 { + t.Errorf("Expected 0 tasks, got %d", len(tasks)) + } +} diff --git a/examples/gno.land/r/demo/todolist/gno.mod b/examples/gno.land/r/demo/todolist/gno.mod new file mode 100644 index 00000000000..563bab74ad5 --- /dev/null +++ b/examples/gno.land/r/demo/todolist/gno.mod @@ -0,0 +1,8 @@ +module gno.land/r/demo/todolist + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest + gno.land/p/demo/todolist v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/todolist/todolist.gno b/examples/gno.land/r/demo/todolist/todolist.gno new file mode 100644 index 00000000000..5790aac630a --- /dev/null +++ b/examples/gno.land/r/demo/todolist/todolist.gno @@ -0,0 +1,176 @@ +package todolistrealm + +import ( + "bytes" + "strconv" + + "gno.land/p/demo/avl" + "gno.land/p/demo/seqid" + "gno.land/p/demo/todolist" + "gno.land/p/demo/ufmt" +) + +// State variables +var ( + todolistTree *avl.Tree + tlid seqid.ID +) + +// Constructor +func init() { + todolistTree = avl.NewTree() +} + +func NewTodoList(title string) (int, string) { + // Create new Todolist + tl := todolist.NewTodoList(title) + // Update AVL tree with new state + tlid.Next() + todolistTree.Set(strconv.Itoa(int(tlid)), tl) + return int(tlid), "created successfully" +} + +func AddTask(todolistID int, title string) string { + // Get Todolist from AVL tree + tl, ok := todolistTree.Get(strconv.Itoa(todolistID)) + if !ok { + panic("Todolist not found") + } + + // get the number of tasks in the todolist + id := tl.(*todolist.TodoList).Tasks.Size() + + // create the task + task := todolist.NewTask(title) + + // Cast raw data from tree into Todolist struct + tl.(*todolist.TodoList).AddTask(id, task) + + return "task added successfully" +} + +func ToggleTaskStatus(todolistID int, taskID int) string { + // Get Todolist from AVL tree + tl, ok := todolistTree.Get(strconv.Itoa(todolistID)) + if !ok { + panic("Todolist not found") + } + + // Get the task from the todolist + task, found := tl.(*todolist.TodoList).Tasks.Get(strconv.Itoa(taskID)) + if !found { + panic("Task not found") + } + + // Change the status of the task + todolist.ToggleTaskStatus(task.(*todolist.Task)) + + return "task status changed successfully" +} + +func RemoveTask(todolistID int, taskID int) string { + // Get Todolist from AVL tree + tl, ok := todolistTree.Get(strconv.Itoa(todolistID)) + if !ok { + panic("Todolist not found") + } + + // Get the task from the todolist + _, ok = tl.(*todolist.TodoList).Tasks.Get(strconv.Itoa(taskID)) + if !ok { + panic("Task not found") + } + + // Change the status of the task + tl.(*todolist.TodoList).RemoveTask(strconv.Itoa(taskID)) + + return "task status changed successfully" +} + +func RemoveTodoList(todolistID int) string { + // Get Todolist from AVL tree + _, ok := todolistTree.Get(strconv.Itoa(todolistID)) + if !ok { + panic("Todolist not found") + } + + // Remove the todolist + todolistTree.Remove(strconv.Itoa(todolistID)) + + return "Todolist removed successfully" +} + +func Render(path string) string { + if path == "" { + return renderHomepage() + } + + return "unknown page" +} + +func renderHomepage() string { + // Define empty buffer + var b bytes.Buffer + + b.WriteString("# Welcome to ToDolist\n\n") + + // If no todolists have been created + if todolistTree.Size() == 0 { + b.WriteString("### No todolists available currently!") + return b.String() + } + + // Iterate through AVL tree + todolistTree.Iterate("", "", func(key string, value interface{}) bool { + // cast raw data from tree into Todolist struct + tl := value.(*todolist.TodoList) + + // Add Todolist name + b.WriteString( + ufmt.Sprintf( + "## Todolist #%s: %s\n", + key, // Todolist ID + tl.GetTodolistTitle(), + ), + ) + + // Add Todolist owner + b.WriteString( + ufmt.Sprintf( + "#### Todolist owner : %s\n", + tl.GetTodolistOwner(), + ), + ) + + // List all todos that are currently Todolisted + if todos := tl.GetTasks(); len(todos) > 0 { + b.WriteString( + ufmt.Sprintf("Currently Todo tasks: %d\n\n", len(todos)), + ) + + for index, todo := range todos { + b.WriteString( + ufmt.Sprintf("#%d - %s ", index, todo.Title), + ) + // displays a checked box if task is marked as done, an empty box if not + if todo.Done { + b.WriteString( + "☑\n\n", + ) + continue + } + + b.WriteString( + "☐\n\n", + ) + } + } else { + b.WriteString("No tasks in this list currently\n") + } + + b.WriteString("\n") + return false + }) + + return b.String() +} diff --git a/examples/gno.land/r/demo/todolist/todolist_test.gno b/examples/gno.land/r/demo/todolist/todolist_test.gno new file mode 100644 index 00000000000..db55110851e --- /dev/null +++ b/examples/gno.land/r/demo/todolist/todolist_test.gno @@ -0,0 +1,86 @@ +package todolistrealm + +import ( + "std" + "strconv" + "testing" + + "gno.land/p/demo/todolist" +) + +var ( + node interface{} + tdl *todolist.TodoList +) + +func TestNewTodoList(t *testing.T) { + title := "My Todo List" + tlid, _ := NewTodoList(title) + if tlid != 1 { + t.Errorf("Expected tlid to be 1, but got %d", tlid) + } + + // get the todolist node from the tree + node, _ = todolistTree.Get(strconv.Itoa(tlid)) + // convert the node to a TodoList struct + tdl = node.(*todolist.TodoList) + + if tdl.Title != title { + t.Errorf("Expected title to be %s, but got %s", title, tdl.Title) + } + if tdl.Owner != std.GetOrigCaller() { + t.Errorf("Expected owner to be %s, but got %s", std.GetOrigCaller(), tdl.Owner) + } + if len(tdl.GetTasks()) != 0 { + t.Errorf("Expected no tasks in the todo list, but got %d tasks", len(tdl.GetTasks())) + } +} + +func TestAddTask(t *testing.T) { + AddTask(1, "Task 1") + + tasks := tdl.GetTasks() + if len(tasks) != 1 { + t.Errorf("Expected 1 task in the todo list, but got %d tasks", len(tasks)) + } + + if tasks[0].Title != "Task 1" { + t.Errorf("Expected task title to be 'Task 1', but got '%s'", tasks[0].Title) + } + + if tasks[0].Done { + t.Errorf("Expected task to be not done, but it is marked as done") + } +} + +func TestToggleTaskStatus(t *testing.T) { + ToggleTaskStatus(1, 0) + task := tdl.GetTasks()[0] + + if !task.Done { + t.Errorf("Expected task to be done, but it is not marked as done") + } + + ToggleTaskStatus(1, 0) + + if task.Done { + t.Errorf("Expected task to be not done, but it is marked as done") + } +} + +func TestRemoveTask(t *testing.T) { + RemoveTask(1, 0) + tasks := tdl.GetTasks() + + if len(tasks) != 0 { + t.Errorf("Expected no tasks in the todo list, but got %d tasks", len(tasks)) + } +} + +func TestRemoveTodoList(t *testing.T) { + RemoveTodoList(1) + + if todolistTree.Size() != 0 { + t.Errorf("Expected no tasks in the todo list, but got %d tasks", todolistTree.Size()) + } +}