chore: added more @spec

This commit is contained in:
2026-05-01 17:49:50 +02:00
parent abcae1dad7
commit 881056eb61
157 changed files with 6223 additions and 1647 deletions

View File

@@ -25,6 +25,7 @@ defmodule BDS.Desktop.AutomationTest do
assert snapshot.assistant_visible == false
assert snapshot.panel_visible == false
assert snapshot.editor_title == "Dashboard"
assert snapshot.activity_labels == [
"Posts",
"Pages",
@@ -37,6 +38,7 @@ defmodule BDS.Desktop.AutomationTest do
"Git",
"Settings"
]
assert "Drafts" in snapshot.sidebar_sections
assert "Status" in snapshot.editor_meta_labels
@@ -155,7 +157,10 @@ defmodule BDS.Desktop.AutomationTest do
end
defp automation_process_counts do
%{app: count_processes("scripts/desktop_automation_app\\.exs"), driver: count_processes("desktop_automation_runner\\.mjs")}
%{
app: count_processes("scripts/desktop_automation_app\\.exs"),
driver: count_processes("desktop_automation_runner\\.mjs")
}
end
defp count_processes(pattern) do

View File

@@ -13,7 +13,9 @@ defmodule BDS.Desktop.ImportShellLiveTest do
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
Ecto.Adapters.SQL.Sandbox.mode(BDS.Repo, {:shared, self()})
temp_dir = Path.join(System.tmp_dir!(), "bds-import-shell-live-#{System.unique_integer([:positive])}")
temp_dir =
Path.join(System.tmp_dir!(), "bds-import-shell-live-#{System.unique_integer([:positive])}")
File.mkdir_p!(temp_dir)
on_exit(fn -> File.rm_rf(temp_dir) end)
@@ -23,7 +25,8 @@ defmodule BDS.Desktop.ImportShellLiveTest do
%{project: project, temp_dir: temp_dir}
end
test "opening an import definition renders the dedicated import analysis editor instead of the fallback shell frame", %{project: project, temp_dir: temp_dir} do
test "opening an import definition renders the dedicated import analysis editor instead of the fallback shell frame",
%{project: project, temp_dir: temp_dir} do
uploads_dir = Path.join(temp_dir, "uploads")
wxr_path = Path.join(temp_dir, "legacy.xml")
@@ -89,7 +92,13 @@ defmodule BDS.Desktop.ImportShellLiveTest do
},
post_stats: %{new_count: 1, update_count: 0, conflict_count: 1, duplicate_count: 0},
page_stats: %{new_count: 1, update_count: 0, conflict_count: 0, duplicate_count: 0},
media_stats: %{new_count: 1, update_count: 0, conflict_count: 0, duplicate_count: 0, missing_count: 0},
media_stats: %{
new_count: 1,
update_count: 0,
conflict_count: 0,
duplicate_count: 0,
missing_count: 0
},
category_stats: %{existing_count: 0, mapped_count: 0, new_count: 1},
tag_stats: %{existing_count: 0, mapped_count: 0, new_count: 1},
date_distribution: [%{year: 2024, post_count: 2, media_count: 1}],
@@ -119,7 +128,13 @@ defmodule BDS.Desktop.ImportShellLiveTest do
items: %{
posts: [
%{item_type: "post", title: "Hello World", slug: "hello-world", status: "new"},
%{item_type: "post", title: "Conflict Me", slug: "conflict-me", status: "conflict", resolution: "ignore"}
%{
item_type: "post",
title: "Conflict Me",
slug: "conflict-me",
status: "conflict",
resolution: "ignore"
}
],
pages: [
%{item_type: "page", title: "About", slug: "about", status: "new"}

View File

@@ -4,8 +4,14 @@ defmodule BDS.Desktop.MainWindowTest do
alias BDS.Desktop.MainWindow
setup do
path = Path.join(System.tmp_dir!(), "bds-main-window-state-#{System.unique_integer([:positive])}.json")
path =
Path.join(
System.tmp_dir!(),
"bds-main-window-state-#{System.unique_integer([:positive])}.json"
)
previous = Application.get_env(:bds, :desktop, [])
updated =
previous
|> Keyword.put(:window_state_path, path)
@@ -21,7 +27,9 @@ defmodule BDS.Desktop.MainWindowTest do
%{path: path}
end
test "window options use a smaller safe default and restore persisted size and position", %{path: path} do
test "window options use a smaller safe default and restore persisted size and position", %{
path: path
} do
opts = MainWindow.window_options()
assert opts[:size] == {1280, 780}

View File

@@ -23,7 +23,9 @@ defmodule BDS.Desktop.OverlayTest do
assert language_picker.kind == :language_picker
assert language_picker.source_language == "en"
assert Enum.map(language_picker.available_targets, & &1.code) == ["de", "fr"]
assert Enum.find(language_picker.available_targets, &(&1.code == "de")).has_existing_translation == true
assert Enum.find(language_picker.available_targets, &(&1.code == "de")).has_existing_translation ==
true
gallery = Overlay.open(:post, :gallery, context)
@@ -74,15 +76,59 @@ defmodule BDS.Desktop.OverlayTest do
current_tab: %{type: :post, id: "post-1", title: "Trip Notes", subtitle: "Draft"},
current_post_language: "en",
posts: [
%{id: "post-1", title: "Trip Notes", status: "draft", canonical_url: "/2026/04/26/trip-notes"},
%{id: "post-2", title: "Photo Walk", status: "published", canonical_url: "/2026/04/26/photo-walk"},
%{id: "post-3", title: "Travel Checklist", status: "draft", canonical_url: "/2026/04/20/travel-checklist"},
%{id: "post-4", title: "Packing List", status: "archived", canonical_url: "/2026/03/18/packing-list"}
%{
id: "post-1",
title: "Trip Notes",
status: "draft",
canonical_url: "/2026/04/26/trip-notes"
},
%{
id: "post-2",
title: "Photo Walk",
status: "published",
canonical_url: "/2026/04/26/photo-walk"
},
%{
id: "post-3",
title: "Travel Checklist",
status: "draft",
canonical_url: "/2026/04/20/travel-checklist"
},
%{
id: "post-4",
title: "Packing List",
status: "archived",
canonical_url: "/2026/03/18/packing-list"
}
],
media: [
%{id: "media-1", title: "Cover Shot", original_name: "cover-shot.jpg", is_image: true, thumbnail_url: "/media-thumbnail/media-1", image_url: "/media-thumbnail/media-1?size=large", alt_text: "Cover shot"},
%{id: "media-2", title: "Street Scene", original_name: "street-scene.jpg", is_image: true, thumbnail_url: "/media-thumbnail/media-2", image_url: "/media-thumbnail/media-2?size=large", alt_text: "Street scene"},
%{id: "media-3", title: "Audio Memo", original_name: "memo.m4a", is_image: false, thumbnail_url: nil, image_url: nil, alt_text: nil}
%{
id: "media-1",
title: "Cover Shot",
original_name: "cover-shot.jpg",
is_image: true,
thumbnail_url: "/media-thumbnail/media-1",
image_url: "/media-thumbnail/media-1?size=large",
alt_text: "Cover shot"
},
%{
id: "media-2",
title: "Street Scene",
original_name: "street-scene.jpg",
is_image: true,
thumbnail_url: "/media-thumbnail/media-2",
image_url: "/media-thumbnail/media-2?size=large",
alt_text: "Street scene"
},
%{
id: "media-3",
title: "Audio Memo",
original_name: "memo.m4a",
is_image: false,
thumbnail_url: nil,
image_url: nil,
alt_text: nil
}
],
post_media_ids: ["media-1", "media-2"],
blog_languages: ["en", "de", "fr"],
@@ -90,9 +136,27 @@ defmodule BDS.Desktop.OverlayTest do
language_flags: %{"en" => "GB", "de" => "DE", "fr" => "FR"},
existing_translations: %{"de" => "draft"},
ai_fields: [
%{key: "title", label: "Title", current_value: "Street Scene", suggested_value: "Street Scene at Dusk", locked: false},
%{key: "alt", label: "Alt Text", current_value: "", suggested_value: "Street scene at dusk", locked: false},
%{key: "caption", label: "Caption", current_value: "Busy corner", suggested_value: "A busy corner at dusk", locked: false}
%{
key: "title",
label: "Title",
current_value: "Street Scene",
suggested_value: "Street Scene at Dusk",
locked: false
},
%{
key: "alt",
label: "Alt Text",
current_value: "",
suggested_value: "Street scene at dusk",
locked: false
},
%{
key: "caption",
label: "Caption",
current_value: "Busy corner",
suggested_value: "A busy corner at dusk",
locked: false
}
],
delete_details: %{
entity_name: "Street Scene",

View File

@@ -42,7 +42,9 @@ defmodule BDS.Desktop.ShellCommandsTest do
%{project: project, temp_dir: temp_dir}
end
test "open_in_browser starts preview for the active project and returns a preview url", %{project: project} do
test "open_in_browser starts preview for the active project and returns a preview url", %{
project: project
} do
assert {:ok, result} = ShellCommands.execute("open_in_browser")
assert result.kind == "open_url"
@@ -51,7 +53,10 @@ defmodule BDS.Desktop.ShellCommandsTest do
assert result.project_id == project.id
end
test "validate_translations returns an editor payload with current translation gaps", %{project: project, temp_dir: temp_dir} do
test "validate_translations returns an editor payload with current translation gaps", %{
project: project,
temp_dir: temp_dir
} do
assert {:ok, post} =
BDS.Posts.create_post(%{
project_id: project.id,
@@ -165,7 +170,8 @@ defmodule BDS.Desktop.ShellCommandsTest do
assert is_map(completed.result.payload.summary)
end
test "rebuild_posts_from_files rebuilds embeddings for published posts when semantic similarity is enabled", %{project: project} do
test "rebuild_posts_from_files rebuilds embeddings for published posts when semantic similarity is enabled",
%{project: project} do
assert {:ok, _metadata} =
BDS.Metadata.update_project_metadata(project.id, %{semantic_similarity_enabled: true})
@@ -178,7 +184,9 @@ defmodule BDS.Desktop.ShellCommandsTest do
})
assert {:ok, published_post} = BDS.Posts.publish_post(post.id)
assert BDS.Repo.get_by(BDS.Embeddings.Key, project_id: project.id, post_id: published_post.id) != nil
assert BDS.Repo.get_by(BDS.Embeddings.Key, project_id: project.id, post_id: published_post.id) !=
nil
BDS.Repo.delete_all(BDS.Embeddings.Key)
@@ -186,10 +194,14 @@ defmodule BDS.Desktop.ShellCommandsTest do
completed = wait_for_task(result.task_id, &(&1.status == :completed))
assert completed.group_name == "Maintenance"
assert BDS.Repo.get_by(BDS.Embeddings.Key, project_id: project.id, post_id: published_post.id) != nil
assert BDS.Repo.get_by(BDS.Embeddings.Key, project_id: project.id, post_id: published_post.id) !=
nil
end
test "repair_metadata_diff exposes live in-task progress from the repair worker", %{project: project} do
test "repair_metadata_diff exposes live in-task progress from the repair worker", %{
project: project
} do
original = Application.get_env(:bds, :tasks, [])
Application.put_env(
@@ -216,7 +228,8 @@ defmodule BDS.Desktop.ShellCommandsTest do
progressed =
wait_for_task(
result.task_id,
&(&1.status == :running and is_number(&1.progress) and &1.progress > 0.2 and &1.progress < 1.0),
&(&1.status == :running and is_number(&1.progress) and &1.progress > 0.2 and
&1.progress < 1.0),
5_000
)
@@ -243,7 +256,9 @@ defmodule BDS.Desktop.ShellCommandsTest do
assert is_map(completed.result.payload.summary)
end
test "rebuild_embedding_index exposes live in-task progress while rebuilding posts", %{project: project} do
test "rebuild_embedding_index exposes live in-task progress while rebuilding posts", %{
project: project
} do
original = Application.get_env(:bds, :tasks, [])
original_embeddings = Application.get_env(:bds, :embeddings)
@@ -289,7 +304,8 @@ defmodule BDS.Desktop.ShellCommandsTest do
progressed =
wait_for_task(
result.task_id,
&(&1.status == :running and is_number(&1.progress) and &1.progress > 0.0 and &1.progress < 1.0 and
&(&1.status == :running and is_number(&1.progress) and &1.progress > 0.0 and
&1.progress < 1.0 and
is_binary(&1.message) and String.contains?(&1.message, "/")),
10_000
)
@@ -297,7 +313,11 @@ defmodule BDS.Desktop.ShellCommandsTest do
assert progressed.group_name == "Embeddings"
assert String.contains?(progressed.message, "/")
assert wait_for_task(result.task_id, &(&1.status == :completed and &1.progress == 1.0), 10_000).status ==
assert wait_for_task(
result.task_id,
&(&1.status == :completed and &1.progress == 1.0),
10_000
).status ==
:completed
end
@@ -307,15 +327,19 @@ defmodule BDS.Desktop.ShellCommandsTest do
assert result.kind == "task_queued"
assert result.action == "rebuild_database"
tasks = wait_for_tasks_by_name([
"Rebuild Posts From Files",
"Rebuild Media From Files",
"Rebuild Scripts From Files",
"Rebuild Templates From Files",
"Rebuild Post Links",
"Regenerate Missing Thumbnails",
"Rebuild Embedding Index"
], &(&1.status == :completed))
tasks =
wait_for_tasks_by_name(
[
"Rebuild Posts From Files",
"Rebuild Media From Files",
"Rebuild Scripts From Files",
"Rebuild Templates From Files",
"Rebuild Post Links",
"Regenerate Missing Thumbnails",
"Rebuild Embedding Index"
],
&(&1.status == :completed)
)
assert Enum.all?(tasks, &(&1.group_name == "Maintenance"))
assert Enum.all?(tasks, &(&1.status == :completed))
@@ -429,32 +453,40 @@ defmodule BDS.Desktop.ShellCommandsTest do
_posts_task =
wait_for_named_task(
"Rebuild Posts From Files",
&(&1.status == :running and is_number(&1.progress) and &1.progress > 0.0 and &1.progress < 1.0),
&(&1.status == :running and is_number(&1.progress) and &1.progress > 0.0 and
&1.progress < 1.0),
10_000
)
phase_one_tasks =
BDS.Tasks.list_tasks()
|> Enum.filter(&(&1.name in [
"Rebuild Posts From Files",
"Rebuild Media From Files",
"Rebuild Scripts From Files",
"Rebuild Templates From Files"
]))
|> Enum.filter(
&(&1.name in [
"Rebuild Posts From Files",
"Rebuild Media From Files",
"Rebuild Scripts From Files",
"Rebuild Templates From Files"
])
)
assert Enum.count(phase_one_tasks, &(&1.status == :running)) == 1
assert Enum.find(phase_one_tasks, &(&1.status == :running)).name == "Rebuild Posts From Files"
tasks = wait_for_tasks_by_name([
"Rebuild Posts From Files",
"Rebuild Media From Files",
"Rebuild Scripts From Files",
"Rebuild Templates From Files",
"Rebuild Post Links",
"Regenerate Missing Thumbnails",
"Rebuild Embedding Index"
], &(&1.status == :completed), 20_000)
tasks =
wait_for_tasks_by_name(
[
"Rebuild Posts From Files",
"Rebuild Media From Files",
"Rebuild Scripts From Files",
"Rebuild Templates From Files",
"Rebuild Post Links",
"Regenerate Missing Thumbnails",
"Rebuild Embedding Index"
],
&(&1.status == :completed),
20_000
)
assert Enum.all?(tasks, &(&1.status == :completed))
end
@@ -505,7 +537,8 @@ defmodule BDS.Desktop.ShellCommandsTest do
progressed =
wait_for_named_task(
"Rebuild Posts From Files",
&(&1.status == :running and is_number(&1.progress) and &1.progress > 0.0 and &1.progress < 1.0),
&(&1.status == :running and is_number(&1.progress) and &1.progress > 0.0 and
&1.progress < 1.0),
5_000
)
@@ -515,15 +548,20 @@ defmodule BDS.Desktop.ShellCommandsTest do
assert wait_for_task(progressed.id, &(&1.status == :completed and &1.progress == 1.0), 5_000).status ==
:completed
tasks = wait_for_tasks_by_name([
"Rebuild Posts From Files",
"Rebuild Media From Files",
"Rebuild Scripts From Files",
"Rebuild Templates From Files",
"Rebuild Post Links",
"Regenerate Missing Thumbnails",
"Rebuild Embedding Index"
], &(&1.status == :completed), 20_000)
tasks =
wait_for_tasks_by_name(
[
"Rebuild Posts From Files",
"Rebuild Media From Files",
"Rebuild Scripts From Files",
"Rebuild Templates From Files",
"Rebuild Post Links",
"Regenerate Missing Thumbnails",
"Rebuild Embedding Index"
],
&(&1.status == :completed),
20_000
)
assert Enum.all?(tasks, &(&1.status == :completed))
end
@@ -537,7 +575,11 @@ defmodule BDS.Desktop.ShellCommandsTest do
assert is_binary(result.task_id)
assert is_binary(result.task_group_id)
tasks = wait_for_tasks_by_name(["Reindex Search Text", "Reindex Media Search Text"], &(&1.status == :completed))
tasks =
wait_for_tasks_by_name(
["Reindex Search Text", "Reindex Media Search Text"],
&(&1.status == :completed)
)
assert Enum.all?(tasks, &(&1.group_name == "Search"))
assert Enum.all?(tasks, &(&1.group_id == result.task_group_id))

View File

@@ -8,8 +8,10 @@ defmodule BDS.Desktop.ShellLiveTest do
test "shell live modules use contexts instead of direct Repo.get calls" do
source_files =
[Path.expand("../../../lib/bds/desktop/shell_live.ex", __DIR__) |
Path.wildcard(Path.join(@shell_live_source_root, "**/*.ex"))]
[
Path.expand("../../../lib/bds/desktop/shell_live.ex", __DIR__)
| Path.wildcard(Path.join(@shell_live_source_root, "**/*.ex"))
]
offenders =
source_files
@@ -46,12 +48,20 @@ defmodule BDS.Desktop.ShellLiveTest do
defmodule FakeEndpointModelHttpClient do
def get("https://api.example.test/v1/models", _headers) do
{:ok,
%{status: 200, headers: %{}, body: Jason.encode!(%{"data" => [%{"id" => "gpt-4.1"}, %{"id" => "gpt-4.1-mini"}]})}}
%{
status: 200,
headers: %{},
body: Jason.encode!(%{"data" => [%{"id" => "gpt-4.1"}, %{"id" => "gpt-4.1-mini"}]})
}}
end
def get("http://localhost:11434/v1/models", _headers) do
{:ok,
%{status: 200, headers: %{}, body: Jason.encode!(%{"data" => [%{"id" => "llama3.3"}, %{"id" => "llava:latest"}]})}}
%{
status: 200,
headers: %{},
body: Jason.encode!(%{"data" => [%{"id" => "llama3.3"}, %{"id" => "llava:latest"}]})
}}
end
def get(_url, _headers), do: {:error, :not_found}
@@ -61,8 +71,8 @@ defmodule BDS.Desktop.ShellLiveTest do
use Plug.Router
import Phoenix.ConnTest, except: [post: 2]
plug :match
plug :dispatch
plug(:match)
plug(:dispatch)
post "/v1/chat/completions" do
Process.sleep(300)
@@ -89,7 +99,9 @@ defmodule BDS.Desktop.ShellLiveTest do
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
Ecto.Adapters.SQL.Sandbox.mode(BDS.Repo, {:shared, self()})
temp_dir = Path.join(System.tmp_dir!(), "bds-shell-live-#{System.unique_integer([:positive])}")
temp_dir =
Path.join(System.tmp_dir!(), "bds-shell-live-#{System.unique_integer([:positive])}")
File.mkdir_p!(temp_dir)
on_exit(fn -> File.rm_rf(temp_dir) end)
@@ -158,7 +170,9 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~ ~s(data-sidebar-action="import")
end
test "sidebar create actions follow the old-app post, script, template, and import flows", %{project: project} do
test "sidebar create actions follow the old-app post, script, template, and import flows", %{
project: project
} do
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
post_count_before = Repo.aggregate(Post, :count, :id)
script_count_before = Repo.aggregate(BDS.Scripts.Script, :count, :id)
@@ -218,7 +232,8 @@ defmodule BDS.Desktop.ShellLiveTest do
|> element("[data-testid='sidebar-create-action'][data-sidebar-action='import']")
|> render_click()
assert Repo.aggregate(ImportDefinitions.ImportDefinition, :count, :id) == import_count_before + 1
assert Repo.aggregate(ImportDefinitions.ImportDefinition, :count, :id) ==
import_count_before + 1
created_definition = Repo.one!(ImportDefinitions.ImportDefinition)
assert created_definition.project_id == project.id
@@ -227,19 +242,27 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~ ~s(data-tab-id="#{created_definition.id}")
end
test "shell live refreshes the posts sidebar when the CLI watcher broadcasts an entity change", %{project: project} do
test "shell live refreshes the posts sidebar when the CLI watcher broadcasts an entity change",
%{project: project} do
{:ok, view, html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
refute html =~ "CLI Added Post"
assert {:ok, post} = Posts.create_post(%{project_id: project.id, title: "CLI Added Post"})
Phoenix.PubSub.broadcast(BDS.PubSub, Watcher.topic(), {:entity_changed, %{entity: "post", entity_id: post.id, action: :created}})
Phoenix.PubSub.broadcast(
BDS.PubSub,
Watcher.topic(),
{:entity_changed, %{entity: "post", entity_id: post.id, action: :created}}
)
assert render(view) =~ "CLI Added Post"
end
test "shell live closes stale post and media tabs when the CLI watcher broadcasts deletions", %{project: project, temp_dir: temp_dir} do
test "shell live closes stale post and media tabs when the CLI watcher broadcasts deletions", %{
project: project,
temp_dir: temp_dir
} do
assert {:ok, post} = Posts.create_post(%{project_id: project.id, title: "CLI Delete Post"})
source_path = Path.join(temp_dir, "cli-delete-media.txt")
@@ -264,7 +287,11 @@ defmodule BDS.Desktop.ShellLiveTest do
assert {:ok, :deleted} = Posts.delete_post(post.id)
Phoenix.PubSub.broadcast(BDS.PubSub, Watcher.topic(), {:entity_changed, %{entity: "post", entity_id: post.id, action: :deleted}})
Phoenix.PubSub.broadcast(
BDS.PubSub,
Watcher.topic(),
{:entity_changed, %{entity: "post", entity_id: post.id, action: :deleted}}
)
html = render(view)
refute html =~ ~s(data-tab-type="post")
@@ -285,7 +312,11 @@ defmodule BDS.Desktop.ShellLiveTest do
assert {:ok, :deleted} = Media.delete_media(media.id)
Phoenix.PubSub.broadcast(BDS.PubSub, Watcher.topic(), {:entity_changed, %{entity: "media", entity_id: media.id, action: :deleted}})
Phoenix.PubSub.broadcast(
BDS.PubSub,
Watcher.topic(),
{:entity_changed, %{entity: "media", entity_id: media.id, action: :deleted}}
)
html = render(view)
refute html =~ ~s(data-tab-type="media")
@@ -624,7 +655,14 @@ defmodule BDS.Desktop.ShellLiveTest do
test "shell live renders the legacy git activity badge from remote behind count" do
Application.put_env(:bds, :git_remote_state_provider, fn _project_id, _opts ->
{:ok, %{local_branch: "main", upstream_branch: "origin/main", has_upstream: true, ahead: 0, behind: 7}}
{:ok,
%{
local_branch: "main",
upstream_branch: "origin/main",
has_upstream: true,
ahead: 0,
behind: 7
}}
end)
{:ok, _view, html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
@@ -906,7 +944,8 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~ ~s(style="width: 280px;")
html = render_hook(view, "sync_layout", %{"sidebar_width" => 420, "assistant_sidebar_width" => 480})
html =
render_hook(view, "sync_layout", %{"sidebar_width" => 420, "assistant_sidebar_width" => 480})
assert html =~ ~s(data-testid="sidebar-shell")
assert html =~ ~s(style="width: 420px;")
@@ -928,7 +967,8 @@ defmodule BDS.Desktop.ShellLiveTest do
test "sidebar filters and load more are server-driven", %{project: project} do
seed_sidebar_posts(project.id)
assert {:ok, _tag} = Tags.create_tag(%{project_id: project.id, name: "tech", color: "#112233"})
assert {:ok, _tag} =
Tags.create_tag(%{project_id: project.id, name: "tech", color: "#112233"})
{:ok, view, html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
@@ -937,7 +977,10 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~ ~s(class="sidebar-section-header")
assert html =~ ~s(class="sidebar-actions")
assert html =~ ~s(data-testid="sidebar-load-more")
assert html_position(html, ~s(data-testid="sidebar-load-more")) > html_position(html, ">Archived<")
assert html_position(html, ~s(data-testid="sidebar-load-more")) >
html_position(html, ">Archived<")
refute html =~ ~s(data-testid="sidebar-filter-tag")
assert html =~ "Alpha Post"
refute html =~ "Overflow Post"
@@ -998,9 +1041,18 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~ "Overflow Post"
end
test "project switcher, ui language, dashboard recents, and output log are wired", %{temp_dir: temp_dir} do
{:ok, other_project} = Projects.create_project(%{name: "Second Blog", data_path: Path.join(temp_dir, "second")})
{:ok, recent_post} = Posts.create_post(%{project_id: other_project.id, title: "Recent Shell Post", content: "body"})
test "project switcher, ui language, dashboard recents, and output log are wired", %{
temp_dir: temp_dir
} do
{:ok, other_project} =
Projects.create_project(%{name: "Second Blog", data_path: Path.join(temp_dir, "second")})
{:ok, recent_post} =
Posts.create_post(%{
project_id: other_project.id,
title: "Recent Shell Post",
content: "body"
})
{:ok, view, html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
@@ -1044,13 +1096,22 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~ "Activated Second Blog"
end
test "task button opens tasks and post panels render real link and git data", %{project: project, temp_dir: temp_dir} do
{:ok, target} = Posts.create_post(%{project_id: project.id, title: "Target Post", content: "target body"})
test "task button opens tasks and post panels render real link and git data", %{
project: project,
temp_dir: temp_dir
} do
{:ok, target} =
Posts.create_post(%{project_id: project.id, title: "Target Post", content: "target body"})
{:ok, target} = Posts.publish_post(target.id)
target_href = canonical_post_href(target)
{:ok, source} =
Posts.create_post(%{project_id: project.id, title: "Linking Source", content: "See [Target](#{target_href})"})
Posts.create_post(%{
project_id: project.id,
title: "Linking Source",
content: "See [Target](#{target_href})"
})
{:ok, source} = Posts.publish_post(source.id)
:ok = Posts.rebuild_post_links(project.id)
@@ -1087,7 +1148,10 @@ defmodule BDS.Desktop.ShellLiveTest do
|> render_click()
refute html =~ ~s(class="panel-shell is-hidden")
assert html =~ ~s(<button class="panel-tab active" type="button" phx-click="select_panel_tab" phx-value-tab="tasks">)
assert html =~
~s(<button class="panel-tab active" type="button" phx-click="select_panel_tab" phx-value-tab="tasks">)
assert html =~ ~s(class="task-list") or html =~ "No background tasks running"
end
@@ -1276,7 +1340,9 @@ defmodule BDS.Desktop.ShellLiveTest do
html =
view
|> element("[data-testid='metadata-diff-repair-button'][data-direction='file_to_db'][data-field='title']")
|> element(
"[data-testid='metadata-diff-repair-button'][data-direction='file_to_db'][data-field='title']"
)
|> render_click()
assert html =~ "Repair Metadata Diff"
@@ -1418,7 +1484,9 @@ defmodule BDS.Desktop.ShellLiveTest do
refute orphan_relative_path in Enum.map(diff.orphan_reports, & &1.file_path)
end
test "metadata diff embeddings tab exposes repair actions and clears embedding drift", %{project: project} do
test "metadata diff embeddings tab exposes repair actions and clears embedding drift", %{
project: project
} do
:ok = BDS.Tasks.clear_finished()
assert {:ok, _metadata} =
@@ -1455,7 +1523,9 @@ defmodule BDS.Desktop.ShellLiveTest do
html =
view
|> element("[data-testid='metadata-diff-repair-button'][data-direction='file_to_db'][data-field='content_hash']")
|> element(
"[data-testid='metadata-diff-repair-button'][data-direction='file_to_db'][data-field='content_hash']"
)
|> render_click()
assert html =~ "Repair Metadata Diff"
@@ -1465,15 +1535,24 @@ defmodule BDS.Desktop.ShellLiveTest do
send(view.pid, :refresh_task_status)
_html = render(view)
assert Repo.get_by(BDS.Embeddings.Key, project_id: project.id, post_id: published_post.id) != nil
assert Repo.get_by(BDS.Embeddings.Key, project_id: project.id, post_id: published_post.id) !=
nil
assert {:ok, diff} = BDS.Maintenance.metadata_diff(project.id)
refute Enum.any?(diff.diff_reports, &(&1.entity_type == "embedding" and &1.entity_id == published_post.id))
refute Enum.any?(
diff.diff_reports,
&(&1.entity_type == "embedding" and &1.entity_id == published_post.id)
)
end
test "post tabs render a real editor and drive save publish discard flows", %{project: project} do
assert {:ok, _tag} = Tags.create_tag(%{project_id: project.id, name: "alpha", color: "#112233"})
assert {:ok, _tag} = Tags.create_tag(%{project_id: project.id, name: "beta", color: "#445566"})
assert {:ok, _tag} =
Tags.create_tag(%{project_id: project.id, name: "alpha", color: "#112233"})
assert {:ok, _tag} =
Tags.create_tag(%{project_id: project.id, name: "beta", color: "#445566"})
assert {:ok, _metadata} = Metadata.add_category(project.id, "notes")
assert {:ok, _metadata} = Metadata.add_category(project.id, "guides")
@@ -1632,7 +1711,10 @@ defmodule BDS.Desktop.ShellLiveTest do
defp new_task!(_existing_ids, _name, 0), do: flunk("new task was not created in time")
defp new_task!(existing_ids, name, attempts) do
case Enum.find(BDS.Tasks.list_tasks(), &(&1.name == name and not MapSet.member?(existing_ids, &1.id))) do
case Enum.find(
BDS.Tasks.list_tasks(),
&(&1.name == name and not MapSet.member?(existing_ids, &1.id))
) do
nil ->
Process.sleep(20)
new_task!(existing_ids, name, attempts - 1)
@@ -1642,7 +1724,9 @@ defmodule BDS.Desktop.ShellLiveTest do
end
end
test "published post editor loads body from file and renders markdown-only editor", %{project: project} do
test "published post editor loads body from file and renders markdown-only editor", %{
project: project
} do
{:ok, post} =
Posts.create_post(%{
project_id: project.id,
@@ -1665,14 +1749,22 @@ defmodule BDS.Desktop.ShellLiveTest do
})
assert html =~ ~s(data-testid="post-editor-content")
assert Regex.match?(~r/name="post_editor\[content\]"[^>]*># Heading\s+```elixir\s+IO\.puts\(:ok\)\s+```/s, html)
assert Regex.match?(
~r/name="post_editor\[content\]"[^>]*># Heading\s+```elixir\s+IO\.puts\(:ok\)\s+```/s,
html
)
assert html =~ ~s(data-monaco-language="markdown-with-macros")
assert html =~ ~s(phx-hook="MonacoEditor")
refute html =~ "post-editor-markdown-highlight"
refute html =~ ~s(phx-value-mode="visual")
end
test "media tabs render a real editor and drive explicit save flows", %{project: project, temp_dir: temp_dir} do
test "media tabs render a real editor and drive explicit save flows", %{
project: project,
temp_dir: temp_dir
} do
{:ok, post} =
Posts.create_post(%{
project_id: project.id,
@@ -1767,7 +1859,10 @@ defmodule BDS.Desktop.ShellLiveTest do
assert saved_media.language == "fr"
end
test "media editor follows the old-app translation editing flow", %{project: project, temp_dir: temp_dir} do
test "media editor follows the old-app translation editing flow", %{
project: project,
temp_dir: temp_dir
} do
source_path = Path.join(temp_dir, "hero.txt")
File.write!(source_path, "media body")
@@ -1801,9 +1896,21 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~ ~s(class="editor-content media-editor")
assert html =~ ~s(class="quick-actions-wrapper")
refute html =~ ~s(class="media-editor-form")
assert has_element?(view, "[data-testid='media-editor'] .editor-content.media-editor .media-details")
assert has_element?(view, "[data-testid='media-editor'] .editor-content.media-editor .media-details .media-translations-section")
assert has_element?(view, "[data-testid='media-editor'] .editor-content.media-editor .media-details .linked-posts-section")
assert has_element?(
view,
"[data-testid='media-editor'] .editor-content.media-editor .media-details"
)
assert has_element?(
view,
"[data-testid='media-editor'] .editor-content.media-editor .media-details .media-translations-section"
)
assert has_element?(
view,
"[data-testid='media-editor'] .editor-content.media-editor .media-details .linked-posts-section"
)
html = render_click(view, "edit_media_translation", %{"id" => media.id, "language" => "de"})
@@ -1814,7 +1921,10 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~ ~s(name="media_translation[caption]")
end
test "settings and media editors render localized labels when the UI language changes", %{project: project, temp_dir: temp_dir} do
test "settings and media editors render localized labels when the UI language changes", %{
project: project,
temp_dir: temp_dir
} do
source_path = Path.join(temp_dir, "localized-hero.txt")
File.write!(source_path, "media body")
@@ -1862,7 +1972,8 @@ defmodule BDS.Desktop.ShellLiveTest do
refute media_html =~ "Linked Posts"
end
test "remaining step-5 routes render dedicated editors instead of the generic shell placeholder", %{project: project} do
test "remaining step-5 routes render dedicated editors instead of the generic shell placeholder",
%{project: project} do
assert {:ok, script} =
Scripts.create_script(%{
project_id: project.id,
@@ -2134,7 +2245,9 @@ defmodule BDS.Desktop.ShellLiveTest do
refute html =~ ~s(data-testid="chat-input-container")
end
test "chat editor renders assistant markdown and dispatches assistant navigation actions", %{project: project} do
test "chat editor renders assistant markdown and dispatches assistant navigation actions", %{
project: project
} do
assert {:ok, post} =
Posts.create_post(%{
project_id: project.id,
@@ -2315,7 +2428,10 @@ defmodule BDS.Desktop.ShellLiveTest do
refute render(view) =~ "Delayed response"
end
test "translation validation route renders dedicated cards and fix controls", %{project: project, temp_dir: temp_dir} do
test "translation validation route renders dedicated cards and fix controls", %{
project: project,
temp_dir: temp_dir
} do
assert {:ok, _metadata} =
BDS.Metadata.update_project_metadata(project.id, %{
main_language: "en",
@@ -2375,7 +2491,9 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~ invalid_file_path
end
test "git diff route renders a structured Monaco diff surface for working tree changes", %{temp_dir: temp_dir} do
test "git diff route renders a structured Monaco diff surface for working tree changes", %{
temp_dir: temp_dir
} do
posts_dir = Path.join(temp_dir, "posts")
File.mkdir_p!(posts_dir)
@@ -2424,7 +2542,9 @@ defmodule BDS.Desktop.ShellLiveTest do
assert html =~ ~s(data-selected-settings-section="ai")
end
test "template sidebar exposes old-app style delete control and removes template rows", %{project: project} do
test "template sidebar exposes old-app style delete control and removes template rows", %{
project: project
} do
assert {:ok, template} =
BDS.Templates.create_template(%{
project_id: project.id,
@@ -2471,9 +2591,20 @@ defmodule BDS.Desktop.ShellLiveTest do
sidebar_post(project_id, "beta-post", "Beta Post", now + 2_000, ["design"], ["guides"])
] ++
Enum.map(1..498, fn index ->
sidebar_post(project_id, "filler-#{index}", "Filler #{index}", now - index, ["filler"], ["archive"])
sidebar_post(
project_id,
"filler-#{index}",
"Filler #{index}",
now - index,
["filler"],
["archive"]
)
end) ++
[sidebar_post(project_id, "overflow-post", "Overflow Post", now - 10_000, ["tech"], ["notes"])]
[
sidebar_post(project_id, "overflow-post", "Overflow Post", now - 10_000, ["tech"], [
"notes"
])
]
{count, _rows} = Repo.insert_all(Post, entries)
assert count == length(entries)