D4-7: add UI tests for translation validation, find duplicates, git diff, menu toolbar, import analysis
This commit is contained in:
@@ -182,7 +182,7 @@ All reconciled to follow code. Specs must be self-consistent and match code.
|
|||||||
| D4-4 | editor_script.allium | Editor layout, create defaults | ~~Save, syntax check, run, delete~~ | **Resolved:** 4 tests added — save persists title change + version bump to DB, run executes script without crash, check syntax validates without side effects, delete removes DB record + file |
|
| D4-4 | editor_script.allium | Editor layout, create defaults | ~~Save, syntax check, run, delete~~ | **Resolved:** 4 tests added — save persists title change + version bump to DB, run executes script without crash, check syntax validates without side effects, delete removes DB record + file |
|
||||||
| D4-5 | editor_template.allium | Editor layout, create defaults | ~~Save with validation, validate, delete with references~~ | **Resolved:** 6 tests added in template_editor_live_test.exs covering save with valid Liquid (version bumps), save with invalid Liquid (rejected, version unchanged), validate (no side effects), delete (removes DB row + .liquid file), delete with references (clears post template_slug) |
|
| D4-5 | editor_template.allium | Editor layout, create defaults | ~~Save with validation, validate, delete with references~~ | **Resolved:** 6 tests added in template_editor_live_test.exs covering save with valid Liquid (version bumps), save with invalid Liquid (rejected, version unchanged), validate (no side effects), delete (removes DB row + .liquid file), delete with references (clears post template_slug) |
|
||||||
| D4-6 | editor_tags.allium | Sync/discover, merge | ~~Cloud sizing, color picker, delete confirmation, create form~~ | **Resolved:** 17 tests added covering `tag_font_size`/`tag_style` unit tests, colour picker preset swatches + pick event, create-form end-to-end, delete-confirmation overlay (now opens confirm dialog instead of immediate delete), cloud sizing UI with font-size assertions, and coloured tag inline styles |
|
| D4-6 | editor_tags.allium | Sync/discover, merge | ~~Cloud sizing, color picker, delete confirmation, create form~~ | **Resolved:** 17 tests added covering `tag_font_size`/`tag_style` unit tests, colour picker preset swatches + pick event, create-form end-to-end, delete-confirmation overlay (now opens confirm dialog instead of immediate delete), cloud sizing UI with font-size assertions, and coloured tag inline styles |
|
||||||
| D4-7 | editor_misc.allium | Menu add/save, metadata diff, validation | Menu protection, import analysis, translation fix, duplicate dismiss, git diff |
|
| ~~D4-7~~ | ~~editor_misc.allium~~ | ~~Menu add/save, metadata diff, validation~~ | ~~Menu protection, import analysis, translation fix, duplicate dismiss, git diff~~ | **Resolved:** 26 tests added — misc_editor_test.exs (translation validation 9, find duplicates 5, git diff 4), home_item_protection_test.exs (toolbar disabled states 3), import_shell_live_test.exs (import analysis assertions) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -194,10 +194,8 @@ All reconciled to follow code. Specs must be self-consistent and match code.
|
|||||||
2. **D1-1 through D1-18** — untested invariants/guarantees
|
2. **D1-1 through D1-18** — untested invariants/guarantees
|
||||||
3. **C-1 through C-3** — internal spec inconsistencies (reconcile to code)
|
3. **C-1 through C-3** — internal spec inconsistencies (reconcile to code)
|
||||||
4. ~~**B1-1 through B1-20**~~ — all resolved: chat inline surfaces, auto-translation, settings sections, style tab, published snapshot fields, rendering subsystem (new rendering.allium), 404.html, media translation modal, menu ops, language picker + confirm dialog, script/template publish actions, import + documentation tabs, metadata-diff entity types, task TTL eviction, discard-post-changes, replace-media-file
|
4. ~~**B1-1 through B1-20**~~ — all resolved: chat inline surfaces, auto-translation, settings sections, style tab, published snapshot fields, rendering subsystem (new rendering.allium), 404.html, media translation modal, menu ops, language picker + confirm dialog, script/template publish actions, import + documentation tabs, metadata-diff entity types, task TTL eviction, discard-post-changes, replace-media-file
|
||||||
5. **A2-1 through A2-17** — spec drift (code is normative, update spec)
|
5. ~~**A2-1 through A2-17**~~ — spec drift (code is normative, update spec) — all resolved
|
||||||
6. ~~**D2-1 through D2-17**~~ — all resolved: `max_posts_per_page` constraint, sandboxed execution, transform toast budget, progress throttle, archived→draft/published transitions, AppNoopNotifier, validate_media implementation+tests, content_hash skip on reindex
|
6. ~~**D2-1 through D2-17**~~ — all resolved: `max_posts_per_page` constraint, sandboxed execution, transform toast budget, progress throttle, archived→draft/published transitions, AppNoopNotifier, validate_media implementation+tests, content_hash skip on reindex
|
||||||
7. ~~**D3-1 through D3-11**~~ — all resolved: content=null assertion, old-file-deletion, DNT guard, validation prerequisites (already tested), macro failure degrades to empty, template roundtrip, default categories, FTS multi-language, canonical URL format, German transliteration expansion
|
7. ~~**D3-1 through D3-11**~~ — all resolved: content=null assertion, old-file-deletion, DNT guard, validation prerequisites (already tested), macro failure degrades to empty, template roundtrip, default categories, FTS multi-language, canonical URL format, German transliteration expansion
|
||||||
8. ~~**B2-1 through B2-9**~~ — all resolved: editor_body resolver, single-post reimport, orphan import, dashboard data, missing-thumbnail regen, cache dir, stale-template prune, render labels, generation progress reporting
|
8. ~~**B2-1 through B2-9**~~ — all resolved: editor_body resolver, single-post reimport, orphan import, dashboard data, missing-thumbnail regen, cache dir, stale-template prune, render labels, generation progress reporting
|
||||||
9. **D4-1 through D4-3** — ~~UI test coverage~~ **Resolved (D4-1 via standalone delete_media_translation + MediaDetectLanguage tests; D4-2 via 3 new test files + expanded managed_categories — 56 tests added; D4-3 via WelcomeScreen/CSP/chart surface tests)**
|
9. ~~**D4-1 through D4-7**~~ — all UI test coverage resolved: D4-1 (delete_media_translation + MediaDetectLanguage), D4-2 (MCP, style, search, categories — 56 tests), D4-3 (WelcomeScreen/CSP/chart), D4-4 (script save/run/check/delete), D4-5 (template save/validate/delete), D4-6 (tag editor 17 tests), D4-7 (26 tests: translation validation 9, find duplicates 5, git diff 4, menu toolbar 3, import analysis assertions)
|
||||||
**D4-4 through D4-6** — ~~UI test coverage~~ **Resolved (D4-4 script editor save/run/check/delete tests; D4-5 template editor save/validate/delete tests; D4-6 tag editor 17 tests covering cloud sizing, colour picker, create form, delete confirmation via overlay)**
|
|
||||||
**D4-7** — remaining UI test coverage (menu protection, import analysis, translation fix, duplicate dismiss, git diff)
|
|
||||||
|
|||||||
@@ -89,6 +89,12 @@ defmodule BDS.Desktop.ImportShellLiveTest do
|
|||||||
refute html =~ ~s(name="mapped_to")
|
refute html =~ ~s(name="mapped_to")
|
||||||
refute html =~ "Desktop workbench content routed through the Elixir shell."
|
refute html =~ "Desktop workbench content routed through the Elixir shell."
|
||||||
|
|
||||||
|
assert html =~ "Legacy Blog"
|
||||||
|
assert html =~ "https://legacy.example"
|
||||||
|
assert html =~ ~s(class="import-stat-cards")
|
||||||
|
assert html =~ "Macros (1)"
|
||||||
|
assert html =~ ~s(class="import-execute-btn")
|
||||||
|
|
||||||
_html =
|
_html =
|
||||||
view
|
view
|
||||||
|> element("form:has(input[value='conflict-me'])")
|
|> element("form:has(input[value='conflict-me'])")
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
defmodule BDS.Desktop.MenuEditor.HomeItemProtectionTest do
|
defmodule BDS.Desktop.MenuEditor.HomeItemProtectionTest do
|
||||||
use ExUnit.Case, async: true
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
import Phoenix.LiveViewTest
|
||||||
|
|
||||||
|
alias BDS.Desktop.ShellLive.MenuEditor
|
||||||
alias BDS.Desktop.ShellLive.MenuEditor.{TreeOps, TreePredicates}
|
alias BDS.Desktop.ShellLive.MenuEditor.{TreeOps, TreePredicates}
|
||||||
|
|
||||||
@home_id TreeOps.home_item_id()
|
@home_id TreeOps.home_item_id()
|
||||||
@@ -105,4 +108,101 @@ defmodule BDS.Desktop.MenuEditor.HomeItemProtectionTest do
|
|||||||
assert dropped != state
|
assert dropped != state
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "toolbar disabled states" do
|
||||||
|
test "all conditional toolbar buttons are disabled when home item is selected" do
|
||||||
|
assigns = %{
|
||||||
|
myself: nil,
|
||||||
|
menu_editor: %{
|
||||||
|
draft: nil,
|
||||||
|
selected_id: TreeOps.home_item_id(),
|
||||||
|
title: "Navigation",
|
||||||
|
description: "Manage site navigation",
|
||||||
|
category_titles: %{},
|
||||||
|
has_items?: true,
|
||||||
|
can_move_up?: false,
|
||||||
|
can_move_down?: false,
|
||||||
|
can_indent?: false,
|
||||||
|
can_unindent?: false,
|
||||||
|
can_delete?: false,
|
||||||
|
items: [
|
||||||
|
%{kind: :home, label: "Home", slug: "/", children: [],
|
||||||
|
is_home: true, item_id: TreeOps.home_item_id()},
|
||||||
|
%{kind: :page, label: "About", slug: "about", children: [],
|
||||||
|
is_home: false, item_id: "about"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html = render_component(&MenuEditor.render/1, assigns)
|
||||||
|
|
||||||
|
assert html =~ ~r/data-action="move-up"[^>]*\sdisabled/
|
||||||
|
assert html =~ ~r/data-action="move-down"[^>]*\sdisabled/
|
||||||
|
assert html =~ ~r/data-action="indent"[^>]*\sdisabled/
|
||||||
|
assert html =~ ~r/data-action="unindent"[^>]*\sdisabled/
|
||||||
|
assert html =~ ~r/data-action="delete"[^>]*\sdisabled/
|
||||||
|
end
|
||||||
|
|
||||||
|
test "no conditional toolbar buttons are disabled when a regular item is selected" do
|
||||||
|
assigns = %{
|
||||||
|
myself: nil,
|
||||||
|
menu_editor: %{
|
||||||
|
draft: nil,
|
||||||
|
selected_id: "about",
|
||||||
|
title: "Navigation",
|
||||||
|
description: "Manage site navigation",
|
||||||
|
category_titles: %{},
|
||||||
|
has_items?: true,
|
||||||
|
can_move_up?: true,
|
||||||
|
can_move_down?: true,
|
||||||
|
can_indent?: true,
|
||||||
|
can_unindent?: true,
|
||||||
|
can_delete?: true,
|
||||||
|
items: [
|
||||||
|
%{kind: :home, label: "Home", slug: "/", children: [],
|
||||||
|
is_home: true, item_id: TreeOps.home_item_id()},
|
||||||
|
%{kind: :page, label: "About", slug: "about", children: [],
|
||||||
|
is_home: false, item_id: "about"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html = render_component(&MenuEditor.render/1, assigns)
|
||||||
|
|
||||||
|
refute html =~ ~r/data-action="move-up"[^>]*\sdisabled/
|
||||||
|
refute html =~ ~r/data-action="move-down"[^>]*\sdisabled/
|
||||||
|
refute html =~ ~r/data-action="indent"[^>]*\sdisabled/
|
||||||
|
refute html =~ ~r/data-action="unindent"[^>]*\sdisabled/
|
||||||
|
refute html =~ ~r/data-action="delete"[^>]*\sdisabled/
|
||||||
|
end
|
||||||
|
|
||||||
|
test "non-conditional toolbar buttons have no disabled attribute" do
|
||||||
|
assigns = %{
|
||||||
|
myself: nil,
|
||||||
|
menu_editor: %{
|
||||||
|
draft: nil,
|
||||||
|
selected_id: TreeOps.home_item_id(),
|
||||||
|
title: "Navigation",
|
||||||
|
description: "Manage site navigation",
|
||||||
|
category_titles: %{},
|
||||||
|
has_items?: true,
|
||||||
|
can_move_up?: false,
|
||||||
|
can_move_down?: false,
|
||||||
|
can_indent?: false,
|
||||||
|
can_unindent?: false,
|
||||||
|
can_delete?: false,
|
||||||
|
items: [
|
||||||
|
%{kind: :home, label: "Home", slug: "/", children: [],
|
||||||
|
is_home: true, item_id: TreeOps.home_item_id()}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html = render_component(&MenuEditor.render/1, assigns)
|
||||||
|
|
||||||
|
refute html =~ ~r/data-action="add-entry"[^>]*disabled/
|
||||||
|
refute html =~ ~r/data-action="save"[^>]*disabled/
|
||||||
|
refute html =~ ~r/data-action="add-category-archive"[^>]*disabled/
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
364
test/bds/desktop/misc_editor_test.exs
Normal file
364
test/bds/desktop/misc_editor_test.exs
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
defmodule BDS.Desktop.MiscEditorTest do
|
||||||
|
use ExUnit.Case, async: false
|
||||||
|
|
||||||
|
import Phoenix.LiveViewTest
|
||||||
|
|
||||||
|
alias BDS.Desktop.ShellLive.MiscEditor
|
||||||
|
alias BDS.Projects
|
||||||
|
|
||||||
|
@endpoint BDS.Desktop.Endpoint
|
||||||
|
|
||||||
|
setup 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-misc-editor-test-#{System.unique_integer([:positive])}")
|
||||||
|
|
||||||
|
File.mkdir_p!(temp_dir)
|
||||||
|
on_exit(fn -> File.rm_rf(temp_dir) end)
|
||||||
|
|
||||||
|
{:ok, project} = Projects.create_project(%{name: "Misc Editor Test", data_path: temp_dir})
|
||||||
|
%{project: project, temp_dir: temp_dir}
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "translation_validation rendering" do
|
||||||
|
test "shows summary with correct counts" do
|
||||||
|
html = render_translation_validation(%{
|
||||||
|
summary_text: "Checked DB rows: 5 · Checked files: 3 · Invalid DB rows: 2 · Invalid files: 1",
|
||||||
|
invalid_database_rows: [],
|
||||||
|
invalid_filesystem_files: [],
|
||||||
|
can_fix?: false
|
||||||
|
})
|
||||||
|
|
||||||
|
assert html =~ "Checked DB rows: 5"
|
||||||
|
assert html =~ "Checked files: 3"
|
||||||
|
assert html =~ "Invalid DB rows: 2"
|
||||||
|
assert html =~ "Invalid files: 1"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows database issue cards with correct issue types" do
|
||||||
|
html = render_translation_validation(%{
|
||||||
|
invalid_database_rows: [
|
||||||
|
%{
|
||||||
|
"translation_for" => "post-1",
|
||||||
|
"translation_id" => "trans-1",
|
||||||
|
"canonical_language" => "en",
|
||||||
|
"translation_language" => "en",
|
||||||
|
"title" => "Test Post",
|
||||||
|
"file_path" => "posts/test-post.de.md",
|
||||||
|
"issue" => "same-language-as-canonical"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"translation_for" => "post-2",
|
||||||
|
"canonical_language" => "en",
|
||||||
|
"translation_language" => "de",
|
||||||
|
"title" => "DNT Post",
|
||||||
|
"issue" => "do-not-translate-has-translations"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
can_fix?: true
|
||||||
|
})
|
||||||
|
|
||||||
|
assert html =~ "Translation language matches canonical post language"
|
||||||
|
assert html =~ "Post is marked as do-not-translate but has translations"
|
||||||
|
assert html =~ "trans-1"
|
||||||
|
assert html =~ "posts/test-post.de.md"
|
||||||
|
assert html =~ "Test Post"
|
||||||
|
assert html =~ "DNT Post"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows filesystem issue cards" do
|
||||||
|
html = render_translation_validation(%{
|
||||||
|
invalid_filesystem_files: [
|
||||||
|
%{
|
||||||
|
"translation_for" => "post-3",
|
||||||
|
"canonical_language" => "en",
|
||||||
|
"translation_language" => "fr",
|
||||||
|
"title" => "Missing Source",
|
||||||
|
"file_path" => "posts/missing-source.fr.md",
|
||||||
|
"issue" => "missing_source_post"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
can_fix?: true
|
||||||
|
})
|
||||||
|
|
||||||
|
assert html =~ "Translation points to a missing source post"
|
||||||
|
assert html =~ "Missing Source"
|
||||||
|
assert html =~ "posts/missing-source.fr.md"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "fix button disabled when no issues" do
|
||||||
|
html = render_translation_validation(%{
|
||||||
|
can_fix?: false
|
||||||
|
})
|
||||||
|
|
||||||
|
assert html =~ ~s(data-testid="translation-validation-fix")
|
||||||
|
assert html =~ ~r/data-testid="translation-validation-fix"[^>]*disabled/
|
||||||
|
end
|
||||||
|
|
||||||
|
test "fix button enabled when issues exist" do
|
||||||
|
html = render_translation_validation(%{
|
||||||
|
invalid_database_rows: [
|
||||||
|
%{
|
||||||
|
"translation_for" => "post-1",
|
||||||
|
"issue" => "content-in-database",
|
||||||
|
"canonical_language" => "en",
|
||||||
|
"translation_language" => "de",
|
||||||
|
"title" => "Content in DB",
|
||||||
|
"file_path" => "posts/content-in-db.de.md"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
can_fix?: true
|
||||||
|
})
|
||||||
|
|
||||||
|
assert html =~ ~s(data-testid="translation-validation-fix")
|
||||||
|
refute html =~ ~r/data-testid="translation-validation-fix"[^>]*disabled/
|
||||||
|
end
|
||||||
|
|
||||||
|
test "shows content-in-database issue type" do
|
||||||
|
html = render_translation_validation(%{
|
||||||
|
invalid_database_rows: [
|
||||||
|
%{
|
||||||
|
"translation_for" => "post-4",
|
||||||
|
"issue" => "content-in-database",
|
||||||
|
"canonical_language" => "en",
|
||||||
|
"translation_language" => "fr",
|
||||||
|
"title" => "Published Translation",
|
||||||
|
"file_path" => "posts/published.fr.md"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
can_fix?: true
|
||||||
|
})
|
||||||
|
|
||||||
|
assert html =~ "Published translation has content stuck in DB instead of filesystem"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "revalidate button is always present" do
|
||||||
|
html = render_translation_validation(%{
|
||||||
|
can_fix?: false
|
||||||
|
})
|
||||||
|
|
||||||
|
assert html =~ ~s(data-testid="translation-validation-revalidate")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "database issues section shows empty state when none found" do
|
||||||
|
html = render_translation_validation(%{
|
||||||
|
invalid_database_rows: [],
|
||||||
|
invalid_filesystem_files: [],
|
||||||
|
can_fix?: false
|
||||||
|
})
|
||||||
|
|
||||||
|
assert html =~ "translation-validation-empty"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "all four issue types are rendered with correct labels" do
|
||||||
|
html = render_translation_validation(%{
|
||||||
|
invalid_database_rows: [
|
||||||
|
%{"translation_for" => "p1", "issue" => "missing_source_post", "canonical_language" => "en", "translation_language" => "de"},
|
||||||
|
%{"translation_for" => "p2", "issue" => "same-language-as-canonical", "canonical_language" => "en", "translation_language" => "en"},
|
||||||
|
%{"translation_for" => "p3", "issue" => "do-not-translate-has-translations", "canonical_language" => "en", "translation_language" => "de"},
|
||||||
|
%{"translation_for" => "p4", "issue" => "content-in-database", "canonical_language" => "en", "translation_language" => "fr"}
|
||||||
|
],
|
||||||
|
can_fix?: true
|
||||||
|
})
|
||||||
|
|
||||||
|
assert html =~ "Translation points to a missing source post"
|
||||||
|
assert html =~ "Translation language matches canonical post language"
|
||||||
|
assert html =~ "Post is marked as do-not-translate but has translations"
|
||||||
|
assert html =~ "Published translation has content stuck in DB instead of filesystem"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "language display shows canonical and translation" do
|
||||||
|
html = render_translation_validation(%{
|
||||||
|
invalid_database_rows: [
|
||||||
|
%{
|
||||||
|
"translation_for" => "p1",
|
||||||
|
"issue" => "same-language-as-canonical",
|
||||||
|
"canonical_language" => "en",
|
||||||
|
"translation_language" => "en"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
can_fix?: true
|
||||||
|
})
|
||||||
|
|
||||||
|
assert html =~ "en = en"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "find_duplicates rendering" do
|
||||||
|
test "renders pair rows with similarity badges" do
|
||||||
|
html = render_duplicates([
|
||||||
|
%{"post_id_a" => "a1", "post_id_b" => "b1", "title_a" => "Post Alpha",
|
||||||
|
"title_b" => "Post Beta", "similarity" => 0.95, "exact_match" => false},
|
||||||
|
%{"post_id_a" => "a2", "post_id_b" => "b2", "title_a" => "Post Gamma",
|
||||||
|
"title_b" => "Post Delta", "similarity" => 1.0, "exact_match" => true}
|
||||||
|
])
|
||||||
|
|
||||||
|
assert html =~ "Post Alpha"
|
||||||
|
assert html =~ "Post Beta"
|
||||||
|
assert html =~ "Post Gamma"
|
||||||
|
assert html =~ "Post Delta"
|
||||||
|
assert html =~ "95.0%"
|
||||||
|
assert html =~ "Exact Match"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders checkbox, dismiss buttons, and clickable titles" do
|
||||||
|
html = render_duplicates([
|
||||||
|
%{"post_id_a" => "a1", "post_id_b" => "b1", "title_a" => "Post A",
|
||||||
|
"title_b" => "Post B", "similarity" => 0.92, "exact_match" => false}
|
||||||
|
])
|
||||||
|
|
||||||
|
assert html =~ ~s(type="checkbox")
|
||||||
|
assert html =~ ~s(phx-click="toggle_duplicate_pair")
|
||||||
|
assert html =~ ~s(phx-click="dismiss_duplicate_pair")
|
||||||
|
assert html =~ ~s(phx-click="open_duplicate_post")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders Dismiss Checked button disabled when nothing selected" do
|
||||||
|
html = render_duplicates([
|
||||||
|
%{"post_id_a" => "a1", "post_id_b" => "b1", "title_a" => "Post A",
|
||||||
|
"title_b" => "Post B", "similarity" => 0.92, "exact_match" => false}
|
||||||
|
], MapSet.new())
|
||||||
|
|
||||||
|
assert html =~ ~s(phx-click="dismiss_selected_duplicates")
|
||||||
|
assert html =~ ~r/phx-click="dismiss_selected_duplicates"[^>]*disabled/
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders clickable post title buttons with correct phx-value attributes" do
|
||||||
|
html = render_duplicates([
|
||||||
|
%{"post_id_a" => "post-123", "post_id_b" => "post-456",
|
||||||
|
"title_a" => "Clickable A", "title_b" => "Clickable B",
|
||||||
|
"similarity" => 0.98, "exact_match" => false}
|
||||||
|
])
|
||||||
|
|
||||||
|
assert html =~ ~s(phx-value-id="post-123")
|
||||||
|
assert html =~ ~s(phx-value-id="post-456")
|
||||||
|
assert html =~ ~s(phx-value-title="Clickable A")
|
||||||
|
assert html =~ ~s(phx-value-title="Clickable B")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "arrow separator between post titles" do
|
||||||
|
html = render_duplicates([
|
||||||
|
%{"post_id_a" => "a1", "post_id_b" => "b1", "title_a" => "Left",
|
||||||
|
"title_b" => "Right", "similarity" => 0.95, "exact_match" => false}
|
||||||
|
])
|
||||||
|
|
||||||
|
assert html =~ "Left"
|
||||||
|
assert html =~ "→"
|
||||||
|
assert html =~ "Right"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "git_diff rendering" do
|
||||||
|
test "shows empty state when no files are changed" do
|
||||||
|
html = render_git_diff(%{
|
||||||
|
files: [],
|
||||||
|
empty_message: "No unstaged changes"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert html =~ "No unstaged changes"
|
||||||
|
assert html =~ "git-diff-empty"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders file select dropdown with changed files" do
|
||||||
|
html = render_git_diff(%{
|
||||||
|
files: ["posts/hello.md", "media/photo.jpg"],
|
||||||
|
selected_file_path: "posts/hello.md",
|
||||||
|
active_diff: %{
|
||||||
|
file_path: "posts/hello.md", original: "Hello World",
|
||||||
|
modified: "Hello Universe", error: nil, language: "markdown"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert html =~ ~s(data-testid="git-diff-file-select")
|
||||||
|
assert html =~ ~s(<option value="posts/hello.md")
|
||||||
|
assert html =~ ~s(<option value="media/photo.jpg")
|
||||||
|
refute html =~ "No unstaged changes"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders Monaco editor hook with correct attributes" do
|
||||||
|
html = render_git_diff(%{
|
||||||
|
files: ["posts/hello.md"],
|
||||||
|
selected_file_path: "posts/hello.md",
|
||||||
|
active_diff: %{
|
||||||
|
file_path: "posts/hello.md", original: "Hello World",
|
||||||
|
modified: "Hello Universe", error: nil, language: "markdown"
|
||||||
|
},
|
||||||
|
preferences: %{view_style: "side_by_side", word_wrap: true, hide_unchanged_regions: false}
|
||||||
|
})
|
||||||
|
|
||||||
|
assert html =~ ~s(phx-hook="MonacoDiffEditor")
|
||||||
|
assert html =~ ~s(data-monaco-diff-language="markdown")
|
||||||
|
assert html =~ ~s(data-monaco-diff-view-style="side_by_side")
|
||||||
|
assert html =~ ~s(data-monaco-diff-word-wrap="on")
|
||||||
|
assert html =~ ~s(data-monaco-diff-hide-unchanged="false")
|
||||||
|
assert html =~ ~s(<textarea class="monaco-diff-original")
|
||||||
|
assert html =~ ~s(<textarea class="monaco-diff-modified")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "no edit buttons in read-only git diff view" do
|
||||||
|
html = render_git_diff(%{
|
||||||
|
files: ["test.txt"],
|
||||||
|
selected_file_path: "test.txt",
|
||||||
|
active_diff: %{
|
||||||
|
file_path: "test.txt", original: "old",
|
||||||
|
modified: "new", error: nil, language: "plaintext"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
refute html =~ ~s(phx-click="save)
|
||||||
|
refute html =~ ~s(phx-click="edit)
|
||||||
|
refute html =~ ~s(phx-click="commit)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── Private helpers ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
defp render_translation_validation(overrides) do
|
||||||
|
defaults = %{
|
||||||
|
kind: :translation_validation,
|
||||||
|
title: "Translation Validation",
|
||||||
|
subtitle: "",
|
||||||
|
summary: %{},
|
||||||
|
summary_text: "Checked DB rows: 0 · Checked files: 0 · Invalid DB rows: 0 · Invalid files: 0",
|
||||||
|
invalid_database_rows: [],
|
||||||
|
invalid_filesystem_files: [],
|
||||||
|
can_fix?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
assigns = %{myself: nil, misc_editor: Map.merge(defaults, overrides)}
|
||||||
|
render_component(&MiscEditor.render/1, assigns)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp render_duplicates(pairs, selected_pairs \\ MapSet.new()) do
|
||||||
|
defaults = %{
|
||||||
|
kind: :find_duplicates,
|
||||||
|
title: "Find Duplicates",
|
||||||
|
subtitle: "",
|
||||||
|
summary: %{total_pairs: length(pairs)},
|
||||||
|
pairs: pairs,
|
||||||
|
selected_pairs: selected_pairs
|
||||||
|
}
|
||||||
|
|
||||||
|
assigns = %{myself: nil, misc_editor: defaults}
|
||||||
|
render_component(&MiscEditor.render/1, assigns)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp render_git_diff(overrides) do
|
||||||
|
defaults = %{
|
||||||
|
kind: :git_diff,
|
||||||
|
title: "Git Diff",
|
||||||
|
subtitle: "",
|
||||||
|
summary: %{},
|
||||||
|
files: [],
|
||||||
|
selected_file_path: nil,
|
||||||
|
active_diff: %{file_path: nil, original: "", modified: "", error: nil, language: "plaintext"},
|
||||||
|
preferences: %{view_style: "inline", word_wrap: false, hide_unchanged_regions: false},
|
||||||
|
empty_message: "No unstaged changes"
|
||||||
|
}
|
||||||
|
|
||||||
|
assigns = %{myself: nil, misc_editor: Map.merge(defaults, overrides)}
|
||||||
|
render_component(&MiscEditor.render/1, assigns)
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user