integrate R module and add url editor ui
Replaces hardcoded paths with R module throughout: - Shop components: layout nav, cart, product links - Controllers: cart, checkout, contact, seo, order lookup - Shop pages: collection, product, search, checkout success, etc. - Site context: nav item url resolution Admin URL management: - Settings page: prefix editor with validation feedback - Page renderer: url_editor component for page URLs - CSS for url editor styling Test updates for cache isolation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -605,6 +605,65 @@ defmodule Berrypod.PagesTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "update_page_url_slug/2" do
|
||||
test "updates url_slug for system page" do
|
||||
{:ok, updated} = Pages.update_page_url_slug("about", "our-story")
|
||||
|
||||
assert updated.url_slug == "our-story"
|
||||
assert updated.slug == "about"
|
||||
end
|
||||
|
||||
test "creates redirect when url_slug changes" do
|
||||
{:ok, _} = Pages.update_page_url_slug("about", "our-story")
|
||||
|
||||
assert {:ok, redirect} = Berrypod.Redirects.lookup("/about")
|
||||
assert redirect.to_path == "/our-story"
|
||||
end
|
||||
|
||||
test "clears url_slug when set to empty string" do
|
||||
{:ok, _} = Pages.update_page_url_slug("about", "our-story")
|
||||
{:ok, cleared} = Pages.update_page_url_slug("about", "")
|
||||
|
||||
assert cleared.url_slug == nil
|
||||
end
|
||||
|
||||
test "creates system page in DB if it doesn't exist yet" do
|
||||
# About page hasn't been saved to DB, only exists as defaults
|
||||
assert Pages.get_page_struct("delivery") == nil
|
||||
|
||||
{:ok, updated} = Pages.update_page_url_slug("delivery", "shipping")
|
||||
|
||||
assert updated.url_slug == "shipping"
|
||||
assert Pages.get_page_struct("delivery") != nil
|
||||
end
|
||||
|
||||
test "returns error for non-existent custom page" do
|
||||
assert {:error, :not_found} = Pages.update_page_url_slug("nope", "anything")
|
||||
end
|
||||
|
||||
test "deletes stale redirect when new URL becomes live" do
|
||||
# Create a redirect pointing FROM /my-url
|
||||
Berrypod.Redirects.create_auto(%{
|
||||
from_path: "/my-url",
|
||||
to_path: "/somewhere-else",
|
||||
source: "manual"
|
||||
})
|
||||
|
||||
# Now make /my-url a live page
|
||||
{:ok, _} = Pages.update_page_url_slug("about", "my-url")
|
||||
|
||||
# The stale redirect should be gone
|
||||
assert :not_found = Berrypod.Redirects.lookup("/my-url")
|
||||
end
|
||||
|
||||
test "invalidates R cache so new URL resolves immediately" do
|
||||
{:ok, _} = Pages.update_page_url_slug("cart", "basket")
|
||||
|
||||
# R.cart() should now return /basket
|
||||
assert BerrypodWeb.R.cart() == "/basket"
|
||||
end
|
||||
end
|
||||
|
||||
describe "duplicate_custom_page/1" do
|
||||
test "creates a draft copy with -copy slug" do
|
||||
{:ok, original} =
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
defmodule Berrypod.SiteTest do
|
||||
use Berrypod.DataCase, async: true
|
||||
use Berrypod.DataCase, async: false
|
||||
|
||||
alias Berrypod.Site
|
||||
alias Berrypod.Site.SocialLink
|
||||
@@ -274,4 +274,49 @@ defmodule Berrypod.SiteTest do
|
||||
assert Site.show_newsletter?() == true
|
||||
end
|
||||
end
|
||||
|
||||
describe "nav_items_for_shop/1" do
|
||||
test "resolves system page URLs through R module" do
|
||||
# Set a custom URL for the about page
|
||||
{:ok, _} = Berrypod.Pages.update_page_url_slug("about", "our-story")
|
||||
|
||||
# Create a nav item pointing to /about (the default URL)
|
||||
{:ok, _} = Site.create_nav_item(%{location: "header", label: "About", url: "/about"})
|
||||
|
||||
# The resolved URL should use the custom slug
|
||||
[item] = Site.nav_items_for_shop(:header)
|
||||
assert item["href"] == "/our-story"
|
||||
end
|
||||
|
||||
test "resolves home page URL" do
|
||||
{:ok, _} = Site.create_nav_item(%{location: "header", label: "Home", url: "/"})
|
||||
|
||||
[item] = Site.nav_items_for_shop(:header)
|
||||
assert item["href"] == "/"
|
||||
end
|
||||
|
||||
test "preserves external URLs" do
|
||||
{:ok, _} =
|
||||
Site.create_nav_item(%{
|
||||
location: "header",
|
||||
label: "External",
|
||||
url: "https://example.com"
|
||||
})
|
||||
|
||||
[item] = Site.nav_items_for_shop(:header)
|
||||
assert item["href"] == "https://example.com"
|
||||
end
|
||||
|
||||
test "preserves special routes like /collections/all" do
|
||||
{:ok, _} =
|
||||
Site.create_nav_item(%{
|
||||
location: "header",
|
||||
label: "Shop",
|
||||
url: "/collections/all"
|
||||
})
|
||||
|
||||
[item] = Site.nav_items_for_shop(:header)
|
||||
assert item["href"] == "/collections/all"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
defmodule BerrypodWeb.Plugs.BrokenUrlTrackerTest do
|
||||
use BerrypodWeb.ConnCase, async: true
|
||||
|
||||
alias Berrypod.Redirects
|
||||
import Berrypod.AccountsFixtures
|
||||
|
||||
alias Berrypod.{Redirects, Settings}
|
||||
|
||||
setup do
|
||||
Redirects.create_table()
|
||||
# Create admin user so SetupHook allows access
|
||||
user_fixture()
|
||||
# Mark site as live so requests aren't redirected to /coming-soon
|
||||
{:ok, _} = Settings.set_site_live(true)
|
||||
:ok
|
||||
end
|
||||
|
||||
test "records broken URL on 404", %{conn: conn} do
|
||||
# Multi-segment path — not caught by the /:slug catch-all route
|
||||
conn = get(conn, "/zz/nonexistent-path")
|
||||
|
||||
assert conn.status in [404, 500]
|
||||
# Multi-segment path goes through the catch-all route and raises NotFoundError.
|
||||
# The BrokenUrlTracker plug catches this and records it before re-raising.
|
||||
assert_error_sent :not_found, fn ->
|
||||
get(conn, "/zz/nonexistent-path")
|
||||
end
|
||||
|
||||
[broken_url] = Redirects.list_broken_urls()
|
||||
assert broken_url.path == "/zz/nonexistent-path"
|
||||
@@ -20,7 +27,11 @@ defmodule BerrypodWeb.Plugs.BrokenUrlTrackerTest do
|
||||
end
|
||||
|
||||
test "skips static asset paths", %{conn: conn} do
|
||||
get(conn, "/assets/missing-file.js")
|
||||
# Static asset paths should not be recorded as broken URLs.
|
||||
# These raise NotFoundError but the tracker ignores them.
|
||||
assert_error_sent :not_found, fn ->
|
||||
get(conn, "/assets/missing-file.js")
|
||||
end
|
||||
|
||||
assert Redirects.list_broken_urls() == []
|
||||
end
|
||||
|
||||
@@ -32,9 +32,23 @@ defmodule BerrypodWeb.ConnCase do
|
||||
end
|
||||
|
||||
setup tags do
|
||||
Berrypod.DataCase.setup_sandbox(tags)
|
||||
pid = Berrypod.DataCase.setup_sandbox(tags)
|
||||
Berrypod.Settings.SettingsCache.invalidate_all()
|
||||
{:ok, conn: Phoenix.ConnTest.build_conn()}
|
||||
# Clear caches without re-warming from DB (which would bypass sandbox)
|
||||
BerrypodWeb.R.clear()
|
||||
Berrypod.Pages.PageCache.invalidate_all()
|
||||
Berrypod.Redirects.clear_cache()
|
||||
|
||||
# Add sandbox metadata to conn so Phoenix.Ecto.SQL.Sandbox plug
|
||||
# can allow LiveView processes to access the test's DB connection
|
||||
metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for(Berrypod.Repo, pid)
|
||||
encoded_metadata = Phoenix.Ecto.SQL.Sandbox.encode_metadata(metadata)
|
||||
|
||||
conn =
|
||||
Phoenix.ConnTest.build_conn()
|
||||
|> Plug.Conn.put_req_header("user-agent", encoded_metadata)
|
||||
|
||||
{:ok, conn: conn}
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -30,15 +30,21 @@ defmodule Berrypod.DataCase do
|
||||
setup tags do
|
||||
Berrypod.DataCase.setup_sandbox(tags)
|
||||
Berrypod.Settings.SettingsCache.invalidate_all()
|
||||
# Clear caches without re-warming from DB (which would bypass sandbox)
|
||||
BerrypodWeb.R.clear()
|
||||
Berrypod.Pages.PageCache.invalidate_all()
|
||||
Berrypod.Redirects.clear_cache()
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Sets up the sandbox based on the test tags.
|
||||
Returns the owner pid for use in metadata generation.
|
||||
"""
|
||||
def setup_sandbox(tags) do
|
||||
pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Berrypod.Repo, shared: not tags[:async])
|
||||
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)
|
||||
pid
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
Reference in New Issue
Block a user