feat: TUI tags panels (cloud, manage, merge) wired to the shared tags backend (issue #34)

This commit is contained in:
2026-07-17 14:50:25 +02:00
parent 0f3f1efa08
commit 2fd132e827
11 changed files with 1987 additions and 661 deletions

View File

@@ -966,6 +966,143 @@ defmodule BDS.TUITest do
do: Enum.at(state.settings_form.fields, state.settings_form.selected)
end
describe "tags panel (issue #34)" do
defp open_tags_section(state, section) do
state = press(state, "5")
index =
Enum.find_index(state.items, fn
{:item, %{id: id}} -> id == "tags-" <> section
_other -> false
end)
assert index != nil
press(%{state | selected: index}, "enter")
end
defp type_text(state, text) do
text
|> String.graphemes()
|> Enum.reduce(state, fn char, acc -> press(acc, char) end)
end
defp tag_names(project_id) do
project_id |> BDS.Tags.list_tags() |> Enum.map(& &1.name)
end
test "the sidebar entries open the panel sections in the main area", %{project: project} do
{:ok, _tag} = BDS.Tags.create_tag(%{project_id: project.id, name: "elixir"})
state = open_tags_section(mount!(), "cloud")
assert state.tags_panel.section == "cloud"
assert screen_text(state) =~ "elixir (0)"
state = press(state, "esc")
assert state.tags_panel == nil
state = open_tags_section(state, "manage")
assert state.tags_panel.section == "manage"
state = open_tags_section(press(state, "esc"), "merge")
assert state.tags_panel.section == "merge"
assert screen_text(state) =~ "[ ] elixir (0)"
end
test "manage creates a tag through the n prompt", %{project: project} do
state = open_tags_section(mount!(), "manage")
state = press(state, "n")
assert state.tags_panel.input.kind == :new
state = state |> type_text("neu") |> press("enter")
assert "neu" in tag_names(project.id)
assert state.tags_panel.input == nil
assert state.status =~ "neu"
end
test "manage renames the selected tag through the enter prompt", %{project: project} do
{:ok, _tag} = BDS.Tags.create_tag(%{project_id: project.id, name: "alpha"})
state = open_tags_section(mount!(), "manage")
state = press(state, "enter")
assert state.tags_panel.input.kind == :rename
assert state.tags_panel.input.value == "alpha"
state =
Enum.reduce(1..5, state, fn _n, acc -> press(acc, "backspace") end)
_state = state |> type_text("beta") |> press("enter")
assert tag_names(project.id) == ["beta"]
end
test "manage cycles the tag colour with c", %{project: project} do
{:ok, tag} = BDS.Tags.create_tag(%{project_id: project.id, name: "colored"})
state = open_tags_section(mount!(), "manage")
_state = press(state, "c")
updated = BDS.Repo.get!(BDS.Tags.Tag, tag.id)
assert updated.color == List.first(BDS.UI.TagsPanel.colour_presets())
end
test "manage deletes only after y confirmation", %{project: project} do
{:ok, _tag} = BDS.Tags.create_tag(%{project_id: project.id, name: "doomed"})
state = open_tags_section(mount!(), "manage")
# Any other key cancels the pending delete.
state = state |> press("d") |> press("x")
assert state.tags_panel.confirm_delete == nil
assert "doomed" in tag_names(project.id)
state = state |> press("d")
assert state.tags_panel.confirm_delete == "doomed"
state = press(state, "y")
assert tag_names(project.id) == []
assert state.status =~ "doomed"
end
test "merge marks tags with space and merges them into the selection", %{project: project} do
{:ok, _one} = BDS.Tags.create_tag(%{project_id: project.id, name: "one"})
{:ok, _two} = BDS.Tags.create_tag(%{project_id: project.id, name: "two"})
{:ok, tagged_post} =
BDS.Posts.create_post(%{
project_id: project.id,
title: "Tagged",
content: "body",
tags: ["one"]
})
state = open_tags_section(mount!(), "merge")
# Mark "one", move to "two", merge — "one" is folded into "two".
_state = state |> press(" ") |> press("down") |> press("m")
assert tag_names(project.id) == ["two"]
assert BDS.Posts.get_post!(tagged_post.id).tags == ["two"]
end
test "manage syncs tags from post tags with s", %{project: project} do
{:ok, _post} =
BDS.Posts.create_post(%{
project_id: project.id,
title: "With Tags",
content: "body",
tags: ["from-post"]
})
state = open_tags_section(mount!(), "manage")
state = press(state, "s")
assert "from-post" in tag_names(project.id)
assert Enum.any?(state.tags_panel.tags, &(&1.name == "from-post"))
end
end
test "local tui mode stops the VM when the app exits" do
parent = self()
state = mount!(stop_vm_on_exit: true, stop_fun: fn -> send(parent, :vm_stopped) end)