feat: switch to phoenix liveview
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
42
test/bds/desktop/shell_live_test.exs
Normal file
42
test/bds/desktop/shell_live_test.exs
Normal file
@@ -0,0 +1,42 @@
|
||||
defmodule BDS.Desktop.ShellLiveTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
import Phoenix.ConnTest
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
@endpoint BDS.Desktop.Endpoint
|
||||
|
||||
test "shell live owns pane visibility and activity selection on the server" do
|
||||
{:ok, view, html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
|
||||
|
||||
assert html =~ ~s(data-testid="sidebar-shell")
|
||||
assert html =~ ~s(class="panel-shell is-hidden")
|
||||
assert html =~ ~s(data-testid="activity-button")
|
||||
assert html =~ ~s(data-view="posts")
|
||||
assert html =~ ~s(data-view="media")
|
||||
assert html =~ ~s(aria-label="Posts")
|
||||
|
||||
html =
|
||||
view
|
||||
|> element("[data-testid='toggle-sidebar']")
|
||||
|> render_click()
|
||||
|
||||
assert html =~ ~s(class="sidebar-shell is-hidden")
|
||||
|
||||
html =
|
||||
view
|
||||
|> element("[data-testid='toggle-panel']")
|
||||
|> render_click()
|
||||
|
||||
assert html =~ ~s(data-region="panel")
|
||||
refute html =~ ~s(class="panel-shell is-hidden")
|
||||
|
||||
html =
|
||||
view
|
||||
|> element("[data-testid='activity-button'][data-view='media']")
|
||||
|> render_click()
|
||||
|
||||
assert html =~ ~s(aria-label="Media")
|
||||
assert html =~ ~s(data-view="media")
|
||||
end
|
||||
end
|
||||
@@ -3,10 +3,6 @@ defmodule BDS.DesktopTest do
|
||||
|
||||
import Plug.Test
|
||||
|
||||
defmodule TestFolderPicker do
|
||||
def choose_directory(_prompt), do: Process.get(:test_folder_picker_response, :cancel)
|
||||
end
|
||||
|
||||
test "desktop configuration no longer uses a pending adapter" do
|
||||
assert Application.get_env(:bds, BDS.Application)[:desktop_adapter] == :desktop
|
||||
end
|
||||
@@ -99,220 +95,39 @@ defmodule BDS.DesktopTest do
|
||||
assert menu_item(groups, :metadata_diff).shortcut == nil
|
||||
end
|
||||
|
||||
test "desktop shell html follows the old app frame regions and references bundled assets" do
|
||||
html = BDS.Desktop.ShellController.index_html()
|
||||
|
||||
assert html =~ ~s(class="app")
|
||||
assert html =~ ~s(class="window-titlebar")
|
||||
assert html =~ ~s(class="activity-bar")
|
||||
assert html =~ ~s(class="sidebar")
|
||||
assert html =~ ~s(class="tab-bar")
|
||||
assert html =~ ~s(class="status-bar")
|
||||
assert html =~ ~s(src="/assets/app.js")
|
||||
assert html =~ ~s(href="/assets/app.css")
|
||||
end
|
||||
|
||||
test "desktop router serves the shell without requiring Phoenix endpoint secrets" do
|
||||
test "desktop root html is a LiveView shell and references only the live bootstrap assets" do
|
||||
conn = conn(:get, "/?k=#{Desktop.Auth.login_key()}")
|
||||
conn = BDS.Desktop.Router.call(conn, BDS.Desktop.Router.init([]))
|
||||
conn = BDS.Desktop.Endpoint.call(conn, BDS.Desktop.Endpoint.init([]))
|
||||
|
||||
assert conn.status == 200
|
||||
assert conn.resp_body =~ ~s(class="app")
|
||||
assert conn.resp_body =~ ~s(class="window-titlebar")
|
||||
assert conn.resp_body =~ ~s(class="activity-bar")
|
||||
assert conn.resp_body =~ ~s(class="sidebar")
|
||||
assert conn.resp_body =~ ~s(class="status-bar")
|
||||
assert conn.resp_body =~ ~s(data-phx-main)
|
||||
assert conn.resp_body =~ ~s(src="/assets/live.js")
|
||||
assert conn.resp_body =~ ~s(href="/assets/app.css")
|
||||
refute conn.resp_body =~ ~s(src="/assets/app.js")
|
||||
end
|
||||
|
||||
test "desktop router exposes live task status for shell polling" do
|
||||
:ok = BDS.Tasks.clear_finished()
|
||||
|
||||
assert {:ok, task} =
|
||||
BDS.Tasks.register_external_task("preview build", %{
|
||||
group_id: "generation",
|
||||
group_name: "Generation"
|
||||
})
|
||||
|
||||
on_exit(fn ->
|
||||
_ = BDS.Tasks.complete_task(task.id)
|
||||
_ = BDS.Tasks.clear_finished()
|
||||
end)
|
||||
|
||||
assert :ok = BDS.Tasks.report_progress(task.id, 0.5, "halfway")
|
||||
|
||||
conn = conn(:get, "/api/tasks?k=#{Desktop.Auth.login_key()}")
|
||||
conn = BDS.Desktop.Router.call(conn, BDS.Desktop.Router.init([]))
|
||||
test "desktop endpoint serves the live shell without extra router-side secret injection" do
|
||||
conn = conn(:get, "/?k=#{Desktop.Auth.login_key()}")
|
||||
conn = BDS.Desktop.Endpoint.call(conn, BDS.Desktop.Endpoint.init([]))
|
||||
|
||||
assert conn.status == 200
|
||||
assert Plug.Conn.get_resp_header(conn, "content-type") == ["application/json; charset=utf-8"]
|
||||
|
||||
payload = Jason.decode!(conn.resp_body)
|
||||
|
||||
assert payload["active_count"] >= 1
|
||||
assert payload["running_task_message"] == "preview build: halfway"
|
||||
|
||||
assert Enum.any?(payload["tasks"], fn item ->
|
||||
item["id"] == task.id and item["group_name"] == "Generation" and item["progress"] == 0.5
|
||||
end)
|
||||
assert conn.resp_body =~ ~s(data-phx-main)
|
||||
end
|
||||
|
||||
test "desktop router encodes failed task snapshots even when the task error is a tuple" do
|
||||
:ok = BDS.Tasks.clear_finished()
|
||||
|
||||
assert {:ok, task} =
|
||||
BDS.Tasks.submit_task(
|
||||
"broken rebuild",
|
||||
fn _report ->
|
||||
{:error, {{:badkey, "slug"}, [{BDS.Posts, :upsert_post_from_file, 3, [line: 644]}]}}
|
||||
end,
|
||||
%{group_id: "maintenance", group_name: "Maintenance"}
|
||||
)
|
||||
|
||||
on_exit(fn ->
|
||||
_ = BDS.Tasks.clear_finished()
|
||||
end)
|
||||
|
||||
failed = wait_for_task(task.id, &(&1.status == :failed and &1.error != nil))
|
||||
|
||||
conn = conn(:get, "/api/tasks?k=#{Desktop.Auth.login_key()}")
|
||||
conn = BDS.Desktop.Router.call(conn, BDS.Desktop.Router.init([]))
|
||||
test "desktop endpoint exposes a simple health route" do
|
||||
conn = conn(:get, "/health?k=#{Desktop.Auth.login_key()}")
|
||||
conn = BDS.Desktop.Endpoint.call(conn, BDS.Desktop.Endpoint.init([]))
|
||||
|
||||
assert conn.status == 200
|
||||
payload = Jason.decode!(conn.resp_body)
|
||||
|
||||
assert Enum.any?(payload["tasks"], fn item ->
|
||||
item["id"] == failed.id and item["status"] == "failed" and is_binary(item["error"])
|
||||
end)
|
||||
|
||||
assert Enum.any?(payload["tasks"], fn item ->
|
||||
item["id"] == failed.id and String.contains?(item["error"], "badkey")
|
||||
end)
|
||||
assert conn.resp_body == "ok"
|
||||
end
|
||||
|
||||
test "desktop router exposes projects for shell project selection and creation" do
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||
BDS.Repo.delete_all(BDS.Projects.Project)
|
||||
|
||||
internal_projects_root = "/Users/gb/Projects/bDS2/priv/data/projects"
|
||||
before_internal_dirs =
|
||||
case File.ls(internal_projects_root) do
|
||||
{:ok, entries} -> MapSet.new(entries)
|
||||
{:error, :enoent} -> MapSet.new()
|
||||
end
|
||||
|
||||
temp_dir = Path.join(System.tmp_dir!(), "bds-desktop-projects-#{System.unique_integer([:positive])}")
|
||||
File.mkdir_p!(temp_dir)
|
||||
|
||||
on_exit(fn ->
|
||||
File.rm_rf(temp_dir)
|
||||
end)
|
||||
|
||||
{:ok, project} = BDS.Projects.create_project(%{name: "Desktop Projects", data_path: temp_dir})
|
||||
{:ok, _active} = BDS.Projects.set_active_project(project.id)
|
||||
|
||||
conn = conn(:get, "/api/projects?k=#{Desktop.Auth.login_key()}")
|
||||
conn = BDS.Desktop.Router.call(conn, BDS.Desktop.Router.init([]))
|
||||
|
||||
assert conn.status == 200
|
||||
|
||||
payload = Jason.decode!(conn.resp_body)
|
||||
assert payload["active_project_id"] == project.id
|
||||
assert Enum.any?(payload["projects"], &(&1["id"] == project.id and &1["name"] == "Desktop Projects"))
|
||||
assert Enum.any?(payload["projects"], &(&1["id"] == "default" and &1["name"] == "My Blog"))
|
||||
|
||||
created_data_dir = Path.join(temp_dir, "created-from-shell")
|
||||
create_conn =
|
||||
conn(
|
||||
:post,
|
||||
"/api/projects?k=#{Desktop.Auth.login_key()}",
|
||||
Jason.encode!(%{"name" => "Created From Shell", "data_path" => created_data_dir})
|
||||
)
|
||||
|> Plug.Conn.put_req_header("content-type", "application/json")
|
||||
|
||||
create_conn = BDS.Desktop.Router.call(create_conn, BDS.Desktop.Router.init([]))
|
||||
|
||||
assert create_conn.status == 200
|
||||
|
||||
created_payload = Jason.decode!(create_conn.resp_body)
|
||||
assert created_payload["project"]["name"] == "Created From Shell"
|
||||
assert created_payload["active_project_id"] == created_payload["project"]["id"]
|
||||
assert created_payload["project"]["data_path"] == created_data_dir
|
||||
|
||||
after_internal_dirs =
|
||||
case File.ls(internal_projects_root) do
|
||||
{:ok, entries} -> MapSet.new(entries)
|
||||
{:error, :enoent} -> MapSet.new()
|
||||
end
|
||||
|
||||
assert after_internal_dirs == before_internal_dirs
|
||||
end
|
||||
|
||||
test "desktop router lets the shell choose an existing project folder and reuses matching projects" do
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||
|
||||
temp_dir = Path.join(System.tmp_dir!(), "bds-desktop-existing-project-#{System.unique_integer([:positive])}")
|
||||
meta_dir = Path.join(temp_dir, "meta")
|
||||
File.mkdir_p!(meta_dir)
|
||||
|
||||
File.write!(
|
||||
Path.join(meta_dir, "project.json"),
|
||||
Jason.encode!(%{"name" => "Existing Blog", "description" => "Imported from disk"})
|
||||
)
|
||||
|
||||
{:ok, project} = BDS.Projects.create_project(%{name: "Existing Blog", data_path: temp_dir})
|
||||
|
||||
previous_desktop = Application.get_env(:bds, :desktop, [])
|
||||
Application.put_env(:bds, :desktop, Keyword.put(previous_desktop, :folder_picker, TestFolderPicker))
|
||||
Process.put(:test_folder_picker_response, {:ok, temp_dir})
|
||||
|
||||
on_exit(fn ->
|
||||
Application.put_env(:bds, :desktop, previous_desktop)
|
||||
Process.delete(:test_folder_picker_response)
|
||||
File.rm_rf(temp_dir)
|
||||
end)
|
||||
|
||||
conn = conn(:post, "/api/project-folder?k=#{Desktop.Auth.login_key()}")
|
||||
conn = BDS.Desktop.Router.call(conn, BDS.Desktop.Router.init([]))
|
||||
|
||||
assert conn.status == 200
|
||||
|
||||
payload = Jason.decode!(conn.resp_body)
|
||||
|
||||
assert payload["status"] == "ok"
|
||||
assert payload["path"] == temp_dir
|
||||
assert payload["name"] == "Existing Blog"
|
||||
assert payload["description"] == "Imported from disk"
|
||||
assert payload["existing_project_id"] == project.id
|
||||
end
|
||||
|
||||
test "desktop router executes shell commands through the JSON api" do
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.allow(BDS.Repo, self(), Process.whereis(BDS.Preview))
|
||||
|
||||
temp_dir = Path.join(System.tmp_dir!(), "bds-desktop-router-#{System.unique_integer([:positive])}")
|
||||
File.mkdir_p!(temp_dir)
|
||||
|
||||
on_exit(fn ->
|
||||
File.rm_rf(temp_dir)
|
||||
_ = BDS.Preview.stop_preview("default")
|
||||
end)
|
||||
|
||||
{:ok, project} = BDS.Projects.create_project(%{name: "Desktop Router", data_path: temp_dir})
|
||||
{:ok, _project} = BDS.Projects.set_active_project(project.id)
|
||||
|
||||
conn =
|
||||
conn(:post, "/api/commands?k=#{Desktop.Auth.login_key()}", Jason.encode!(%{"action" => "open_in_browser"}))
|
||||
|> Plug.Conn.put_req_header("content-type", "application/json")
|
||||
|
||||
conn = BDS.Desktop.Router.call(conn, BDS.Desktop.Router.init([]))
|
||||
|
||||
assert conn.status == 200
|
||||
assert Plug.Conn.get_resp_header(conn, "content-type") == ["application/json; charset=utf-8"]
|
||||
|
||||
payload = Jason.decode!(conn.resp_body)
|
||||
|
||||
assert payload["result"]["kind"] == "open_url"
|
||||
assert payload["result"]["project_id"] == project.id
|
||||
assert payload["result"]["url"] == "http://127.0.0.1:4123/"
|
||||
end
|
||||
|
||||
test "desktop router serves active-project media thumbnails for the sidebar" do
|
||||
test "desktop endpoint serves active-project media thumbnails for the live sidebar" do
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||
|
||||
temp_dir = Path.join(System.tmp_dir!(), "bds-desktop-thumbnail-#{System.unique_integer([:positive])}")
|
||||
@@ -330,8 +145,8 @@ defmodule BDS.DesktopTest do
|
||||
|
||||
assert {:ok, media} = BDS.Media.import_media(%{project_id: project.id, source_path: source_path})
|
||||
|
||||
conn = conn(:get, "/api/media-thumbnail/#{media.id}?k=#{Desktop.Auth.login_key()}")
|
||||
conn = BDS.Desktop.Router.call(conn, BDS.Desktop.Router.init([]))
|
||||
conn = conn(:get, "/media-thumbnail/#{media.id}?k=#{Desktop.Auth.login_key()}")
|
||||
conn = BDS.Desktop.Endpoint.call(conn, BDS.Desktop.Endpoint.init([]))
|
||||
|
||||
assert conn.status == 200
|
||||
assert [content_type] = Plug.Conn.get_resp_header(conn, "content-type")
|
||||
@@ -349,21 +164,4 @@ defmodule BDS.DesktopTest do
|
||||
Image.new!(3, 2, color: [255, 0, 0])
|
||||
|> Image.write!(:memory, suffix: ".jpg", quality: 85)
|
||||
end
|
||||
|
||||
defp wait_for_task(task_id, matcher, timeout \\ 2_000)
|
||||
|
||||
defp wait_for_task(task_id, _matcher, timeout) when timeout <= 0 do
|
||||
BDS.Tasks.get_task(task_id)
|
||||
end
|
||||
|
||||
defp wait_for_task(task_id, matcher, timeout) do
|
||||
task = BDS.Tasks.get_task(task_id)
|
||||
|
||||
if task && matcher.(task) do
|
||||
task
|
||||
else
|
||||
Process.sleep(50)
|
||||
wait_for_task(task_id, matcher, timeout - 50)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,7 +4,6 @@ defmodule BDS.UI.ShellTest do
|
||||
alias BDS.UI.Commands
|
||||
alias BDS.UI.Registry
|
||||
alias BDS.UI.Session
|
||||
alias BDS.UI.ShellPage
|
||||
alias BDS.UI.Workbench
|
||||
|
||||
test "registry exposes the shared sidebar and editor contracts for the base shell" do
|
||||
@@ -12,6 +11,7 @@ defmodule BDS.UI.ShellTest do
|
||||
editor_routes = Registry.editor_routes()
|
||||
|
||||
assert Registry.default_sidebar_view() == :posts
|
||||
|
||||
assert Enum.map(sidebar_views, & &1.id) == [
|
||||
:posts,
|
||||
:pages,
|
||||
@@ -52,6 +52,7 @@ defmodule BDS.UI.ShellTest do
|
||||
assert restored.active_view == :media
|
||||
assert restored.active_tab == {:media, "media-1"}
|
||||
assert Workbench.dirty?(restored, :post, "post-1") == true
|
||||
|
||||
assert Enum.map(restored.tabs, &{&1.type, &1.id, &1.is_transient}) == [
|
||||
{:post, "post-1", false},
|
||||
{:media, "media-1", true}
|
||||
@@ -96,175 +97,23 @@ defmodule BDS.UI.ShellTest do
|
||||
assert Workbench.dirty?(state, :post, "post-1") == true
|
||||
end
|
||||
|
||||
test "shell page renders the inspectable base app with bootstrap data and shell controls" do
|
||||
html = ShellPage.render()
|
||||
|
||||
assert html =~ ~s(<div class="app" id="bds-shell-app")
|
||||
assert html =~ ~s(data-region="activity-bar")
|
||||
assert html =~ ~s(data-region="sidebar")
|
||||
assert html =~ ~s(data-region="editor")
|
||||
assert html =~ ~s(data-region="status-bar")
|
||||
assert html =~ ~s(data-role="resize-handle")
|
||||
assert html =~ ~s(id="bds-shell-bootstrap")
|
||||
assert html =~ ~s(src="/assets/app.js")
|
||||
assert html =~ ~s(href="/assets/app.css")
|
||||
assert html =~ ~s("task_status")
|
||||
assert html =~ ~s("flag":"🇩🇪")
|
||||
assert html =~ ~s("projects")
|
||||
assert html =~ ~s("id":"default")
|
||||
assert html =~ ~s("name":"My Blog")
|
||||
end
|
||||
|
||||
test "shell page localizes bootstrap content for german ui locale" do
|
||||
previous_lang = System.get_env("LANG")
|
||||
previous_lc_all = System.get_env("LC_ALL")
|
||||
|
||||
on_exit(fn ->
|
||||
restore_env("LANG", previous_lang)
|
||||
restore_env("LC_ALL", previous_lc_all)
|
||||
end)
|
||||
|
||||
System.put_env("LANG", "de_DE.UTF-8")
|
||||
System.delete_env("LC_ALL")
|
||||
|
||||
html = ShellPage.render()
|
||||
|
||||
assert html =~ ~s("ui_language":"de")
|
||||
assert html =~ ~s("catalogs")
|
||||
assert html =~ ~s("File":"Datei")
|
||||
assert html =~ ~s("Dashboard":"Instrumententafel")
|
||||
assert html =~ ~s("Assistant":"Assistent")
|
||||
end
|
||||
|
||||
test "shell bootstrap and static bundle expose the old dashboard sections" do
|
||||
html = ShellPage.render()
|
||||
test "desktop shell keeps the compact frame metrics and live bootstrap assets" do
|
||||
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
|
||||
js = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.js")
|
||||
live_js = File.read!("/Users/gb/Projects/bDS2/priv/ui/live.js")
|
||||
|
||||
assert html =~ ~s("timeline_entries")
|
||||
assert html =~ ~s("tag_cloud_items")
|
||||
assert html =~ ~s("category_counts")
|
||||
assert html =~ ~s("recent_posts")
|
||||
|
||||
assert js =~ "dashboard-content"
|
||||
assert js =~ "dashboard-stats"
|
||||
assert js =~ "timeline-chart"
|
||||
assert js =~ "tag-cloud"
|
||||
assert js =~ "recent-posts-list"
|
||||
|
||||
assert css =~ ".dashboard-content"
|
||||
assert css =~ ".dashboard-stats"
|
||||
assert css =~ ".timeline-chart"
|
||||
assert css =~ ".tag-cloud"
|
||||
assert css =~ ".recent-posts-list"
|
||||
end
|
||||
|
||||
test "shell bootstrap and static bundle expose the old sidebar view contracts" do
|
||||
html = ShellPage.render()
|
||||
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
|
||||
js = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.js")
|
||||
|
||||
assert html =~ ~s("layout":"post_list")
|
||||
assert html =~ ~s("layout":"media_grid")
|
||||
assert html =~ ~s("layout":"entity_list")
|
||||
assert html =~ ~s("layout":"nav_list")
|
||||
|
||||
assert js =~ "renderSidebarPostList"
|
||||
assert js =~ "renderSidebarMediaGrid"
|
||||
assert js =~ "renderSidebarEntityList"
|
||||
assert js =~ "renderSidebarNavList"
|
||||
|
||||
assert css =~ ".sidebar-section-title"
|
||||
assert css =~ ".media-grid"
|
||||
assert css =~ ".chat-list-item"
|
||||
assert css =~ ".settings-nav-entry"
|
||||
end
|
||||
|
||||
test "shell bootstrap and static bundle expose old sidebar filters and the post cutoff contract" do
|
||||
html = ShellPage.render()
|
||||
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
|
||||
js = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.js")
|
||||
|
||||
assert html =~ ~s("filters")
|
||||
assert html =~ ~s("search_placeholder":"sidebar.searchPostsPlaceholder")
|
||||
assert html =~ ~s("search_placeholder":"sidebar.searchMediaPlaceholder")
|
||||
assert html =~ ~s("year_month_counts")
|
||||
assert html =~ ~s("available_tags")
|
||||
assert html =~ ~s("available_categories")
|
||||
assert html =~ ~s("max_items":500)
|
||||
|
||||
assert js =~ "renderSidebarSearchBox"
|
||||
assert js =~ "renderSidebarArchiveFilter"
|
||||
assert js =~ "renderSidebarFilterPanel"
|
||||
assert js =~ "renderSidebarFilterStatus"
|
||||
assert js =~ "applySidebarPostFilters"
|
||||
assert js =~ "applySidebarMediaFilters"
|
||||
assert js =~ "media-thumbnail-image"
|
||||
assert js =~ "/api/media-thumbnail/"
|
||||
assert js =~ "loading=\"lazy\""
|
||||
|
||||
assert css =~ ".search-box"
|
||||
assert css =~ ".filter-panel"
|
||||
assert css =~ ".calendar-view"
|
||||
assert css =~ ".filter-chip"
|
||||
assert css =~ ".filter-status"
|
||||
assert css =~ ".media-thumbnail-image"
|
||||
end
|
||||
|
||||
test "clearing sidebar filters resets from the baseline seed instead of the filtered payload" do
|
||||
js = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.js")
|
||||
|
||||
assert js =~ "sidebarFilterSeeds"
|
||||
assert js =~ "function sidebarFilterSeed(viewId)"
|
||||
assert js =~ "defaultSidebarFilterState(viewId, sidebarFilterSeed(viewId))"
|
||||
refute js =~ "defaultSidebarFilterState(viewId, state.sidebarContent[viewId])"
|
||||
end
|
||||
|
||||
test "sidebar bundle follows the old app header and styling model instead of a subtitle header" do
|
||||
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
|
||||
js = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.js")
|
||||
|
||||
assert js =~ "sidebar-section-header"
|
||||
refute js =~ "sidebar-subtitle"
|
||||
|
||||
assert css =~ "--accent-color: #007acc"
|
||||
assert css =~ "--vscode-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI'"
|
||||
assert css =~ "border-left-color: var(--vscode-focusBorder);"
|
||||
assert css =~ ".sidebar-section-header"
|
||||
refute css =~ ".sidebar-subtitle"
|
||||
end
|
||||
|
||||
test "static shell bundle exists for direct browser inspection" do
|
||||
assert File.exists?("/Users/gb/Projects/bDS2/priv/ui/index.html")
|
||||
assert File.exists?("/Users/gb/Projects/bDS2/priv/ui/app.css")
|
||||
assert File.exists?("/Users/gb/Projects/bDS2/priv/ui/app.js")
|
||||
end
|
||||
|
||||
test "static shell bundle keeps the old compact frame metrics and icon-based controls" do
|
||||
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
|
||||
js = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.js")
|
||||
|
||||
assert File.exists?("/Users/gb/Projects/bDS2/priv/ui/live.js")
|
||||
assert css =~ ".window-titlebar"
|
||||
assert css =~ "height: 34px"
|
||||
assert css =~ "width: 48px"
|
||||
assert css =~ "height: 35px"
|
||||
assert css =~ "height: 22px"
|
||||
|
||||
assert js =~ "window-titlebar-sidebar-icon"
|
||||
assert js =~ "window-titlebar-panel-icon"
|
||||
assert js =~ "window-titlebar-assistant-icon"
|
||||
assert js =~ "toggle-assistant-sidebar"
|
||||
assert js =~ "activity-bar-top"
|
||||
assert js =~ "activity-bar-bottom"
|
||||
assert live_js =~ "LiveView.LiveSocket"
|
||||
assert live_js =~ "Phoenix.Socket"
|
||||
end
|
||||
|
||||
test "static shell bundle hides the fake titlebar menu on macOS and keeps old status-bar alignment rules" do
|
||||
test "desktop shell css keeps the status bar and hidden menu alignment rules" do
|
||||
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
|
||||
js = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.js")
|
||||
|
||||
assert js =~ "navigator.platform"
|
||||
assert js =~ "isMac"
|
||||
assert js =~ "window-titlebar-menu-bar is-hidden"
|
||||
|
||||
assert css =~ ".window-titlebar-menu-bar.is-hidden"
|
||||
assert css =~ "--vscode-statusBar-background: #007acc"
|
||||
@@ -276,95 +125,5 @@ defmodule BDS.UI.ShellTest do
|
||||
assert css =~ ".status-bar-language-select"
|
||||
assert css =~ ".status-bar-item.language-badge"
|
||||
assert css =~ ".status-bar-item.offline-badge"
|
||||
|
||||
assert js =~ "renderLanguageOptions"
|
||||
assert js =~ "language.flag || language.code.toUpperCase()"
|
||||
assert js =~ "status-bar-language-select"
|
||||
assert js =~ "setUiLanguage"
|
||||
end
|
||||
|
||||
test "static shell bundle polls live task status and renders a task-backed lower panel" do
|
||||
js = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.js")
|
||||
|
||||
assert js =~ "/api/tasks"
|
||||
assert js =~ "/api/projects"
|
||||
assert js =~ "/api/commands"
|
||||
assert js =~ "fetchTaskStatus"
|
||||
assert js =~ "executeBackendShellCommand"
|
||||
assert js =~ "applyShellCommandResult"
|
||||
assert js =~ "openTasksPanel"
|
||||
assert js =~ "command === \"open-tasks-panel\")"
|
||||
assert js =~ "openTasksPanel();"
|
||||
assert js =~ "return;"
|
||||
assert js =~ "No background tasks running"
|
||||
assert js =~ "task-list"
|
||||
assert js =~ "output-list"
|
||||
assert js =~ "git-log-list"
|
||||
assert js =~ "data-panel-tab=\"output\""
|
||||
assert js =~ "data-panel-tab=\"git_log\""
|
||||
end
|
||||
|
||||
test "static shell bundle keeps bottom panel tabs in a stable order" do
|
||||
js = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.js")
|
||||
|
||||
assert js =~ "return [\"tasks\", \"output\", \"git_log\", state.session.panel.active_tab].filter(uniqueValue);"
|
||||
end
|
||||
|
||||
test "static shell bundle renders a left-side project field with selection, existing-folder import, and create affordances" do
|
||||
css = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.css")
|
||||
js = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.js")
|
||||
|
||||
assert js =~ "project-selector-trigger"
|
||||
assert js =~ "project-dropdown"
|
||||
assert js =~ "create-project-btn"
|
||||
assert js =~ "existing-project-btn"
|
||||
assert js =~ "/api/project-folder"
|
||||
assert js =~ "fetchProjects"
|
||||
assert js =~ "createProject"
|
||||
assert js =~ "importExistingProject"
|
||||
assert js =~ "selectProject"
|
||||
assert js =~ "toggleProjectMenu"
|
||||
assert js =~ "closeProjectMenu"
|
||||
|
||||
assert css =~ ".project-selector-trigger"
|
||||
assert css =~ ".project-dropdown"
|
||||
assert css =~ ".create-project-btn"
|
||||
assert css =~ ".existing-project-btn"
|
||||
end
|
||||
|
||||
test "static shell bundle uses translation catalogs for visible shell chrome" do
|
||||
js = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.js")
|
||||
|
||||
assert js =~ "translationsForLanguage"
|
||||
assert js =~ "function t("
|
||||
assert js =~ "New Project"
|
||||
assert js =~ "No background tasks running"
|
||||
assert js =~ "Idle"
|
||||
end
|
||||
|
||||
test "static shell bundle binds base shell hotkeys and menu actions to existing shell functionality" do
|
||||
js = File.read!("/Users/gb/Projects/bDS2/priv/ui/app.js")
|
||||
|
||||
assert js =~ "window.addEventListener(\"keydown\""
|
||||
assert js =~ "event.metaKey"
|
||||
assert js =~ "case \"j\""
|
||||
assert js =~ "case \"1\""
|
||||
assert js =~ "case \"2\""
|
||||
assert js =~ "case \"\\\\\""
|
||||
assert js =~ "function isSidebarViewCommand(action)"
|
||||
assert js =~ "function isSingletonEditorCommand(action)"
|
||||
assert js =~ "action.startsWith(\"view_\")"
|
||||
assert js =~ "action.startsWith(\"open_\")"
|
||||
assert js =~ "openSingletonTab(action.slice(5));"
|
||||
assert js =~ "executeBackendShellCommand(action)"
|
||||
assert js =~ "case \"metadata_diff\""
|
||||
assert js =~ "case \"regenerate_calendar\""
|
||||
assert js =~ "case \"fill_missing_translations\""
|
||||
assert js =~ "root.querySelectorAll(\"button[data-command]\")"
|
||||
assert js =~ "[data-close-tab]"
|
||||
assert js =~ "language.flag"
|
||||
end
|
||||
|
||||
defp restore_env(key, nil), do: System.delete_env(key)
|
||||
defp restore_env(key, value), do: System.put_env(key, value)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user