2026-02-18 21:23:15 +00:00
|
|
|
defmodule BerrypodWeb.ThemeCSSConsistencyTest do
|
2026-01-17 16:28:22 +00:00
|
|
|
@moduledoc """
|
2026-01-17 21:43:26 +00:00
|
|
|
Tests that verify CSS works correctly for both the theme editor
|
|
|
|
|
preview and the shop pages using the shared .themed class.
|
|
|
|
|
|
|
|
|
|
Architecture:
|
|
|
|
|
- Both shop pages and preview use .themed class for shared styles
|
2026-02-18 01:15:28 +00:00
|
|
|
- Theme editor uses .preview-frame[data-*] selectors for live switching (in admin.css)
|
|
|
|
|
- Shop pages get theme values via inline CSS from CSSGenerator (shop.css)
|
2026-01-25 11:36:20 +00:00
|
|
|
- Component styles use .themed for shared styling (theme-layer2-attributes.css)
|
2026-01-17 16:28:22 +00:00
|
|
|
"""
|
|
|
|
|
|
2026-02-18 21:23:15 +00:00
|
|
|
use BerrypodWeb.ConnCase, async: false
|
2026-01-17 16:28:22 +00:00
|
|
|
|
|
|
|
|
import Phoenix.LiveViewTest
|
2026-02-18 21:23:15 +00:00
|
|
|
import Berrypod.AccountsFixtures
|
2026-01-17 16:28:22 +00:00
|
|
|
|
2026-02-18 21:23:15 +00:00
|
|
|
alias Berrypod.Settings
|
2026-01-17 16:28:22 +00:00
|
|
|
|
|
|
|
|
setup do
|
|
|
|
|
user = user_fixture()
|
|
|
|
|
%{user: user}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "CSS selector consistency" do
|
2026-02-11 22:58:58 +00:00
|
|
|
test "shop home page has .themed with data attributes", %{conn: conn, user: user} do
|
|
|
|
|
{:ok, _view, html} = live(log_in_user(conn, user), ~p"/")
|
2026-01-17 16:28:22 +00:00
|
|
|
|
2026-01-17 21:43:26 +00:00
|
|
|
# Verify themed element exists with theme data attributes
|
|
|
|
|
assert html =~ ~r/<div[^>]*class="themed/
|
2026-01-17 16:28:22 +00:00
|
|
|
assert html =~ ~r/data-mood="/
|
|
|
|
|
assert html =~ ~r/data-typography="/
|
|
|
|
|
assert html =~ ~r/data-shape="/
|
|
|
|
|
assert html =~ ~r/data-density="/
|
|
|
|
|
end
|
|
|
|
|
|
2026-01-17 21:43:26 +00:00
|
|
|
test "theme editor has .themed with data attributes", %{conn: conn, user: user} do
|
2026-01-17 16:28:22 +00:00
|
|
|
conn = log_in_user(conn, user)
|
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/admin/theme")
|
|
|
|
|
|
2026-01-17 21:43:26 +00:00
|
|
|
# Verify themed element exists in preview-frame with theme data attributes
|
|
|
|
|
assert html =~ ~r/<div[^>]*class="themed/
|
2026-01-17 16:28:22 +00:00
|
|
|
assert html =~ ~r/data-mood="/
|
|
|
|
|
assert html =~ ~r/data-typography="/
|
|
|
|
|
assert html =~ ~r/data-shape="/
|
|
|
|
|
assert html =~ ~r/data-density="/
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "shop page uses same theme settings as preview", %{conn: conn, user: user} do
|
|
|
|
|
# Set a specific theme configuration
|
|
|
|
|
{:ok, _settings} = Settings.apply_preset(:night)
|
|
|
|
|
|
2026-02-11 22:58:58 +00:00
|
|
|
# Check shop page (logged in since site_live is false by default)
|
|
|
|
|
conn = log_in_user(conn, user)
|
2026-01-17 16:28:22 +00:00
|
|
|
{:ok, _view, shop_html} = live(conn, ~p"/")
|
|
|
|
|
|
2026-02-11 22:58:58 +00:00
|
|
|
# Check preview (already authenticated)
|
2026-01-17 16:28:22 +00:00
|
|
|
{:ok, _view, preview_html} = live(conn, ~p"/admin/theme")
|
|
|
|
|
|
|
|
|
|
# Extract data-mood values from both
|
|
|
|
|
[_, shop_mood] = Regex.run(~r/data-mood="([^"]+)"/, shop_html)
|
|
|
|
|
[_, preview_mood] = Regex.run(~r/data-mood="([^"]+)"/, preview_html)
|
|
|
|
|
|
|
|
|
|
# They should match
|
|
|
|
|
assert shop_mood == preview_mood
|
|
|
|
|
assert shop_mood == "dark"
|
|
|
|
|
end
|
|
|
|
|
|
2026-02-11 22:58:58 +00:00
|
|
|
test "theme settings changes are reflected on shop page", %{conn: conn, user: user} do
|
|
|
|
|
conn = log_in_user(conn, user)
|
|
|
|
|
|
2026-01-17 16:28:22 +00:00
|
|
|
# Start with minimal preset (neutral mood)
|
|
|
|
|
{:ok, _settings} = Settings.apply_preset(:minimal)
|
|
|
|
|
|
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/")
|
|
|
|
|
assert html =~ ~s(data-mood="neutral")
|
|
|
|
|
|
|
|
|
|
# Change to night preset (dark mood)
|
|
|
|
|
{:ok, _settings} = Settings.apply_preset(:night)
|
|
|
|
|
|
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/")
|
|
|
|
|
assert html =~ ~s(data-mood="dark")
|
|
|
|
|
|
|
|
|
|
# Change to gallery preset (warm mood)
|
|
|
|
|
{:ok, _settings} = Settings.apply_preset(:gallery)
|
|
|
|
|
|
|
|
|
|
{:ok, _view, html} = live(conn, ~p"/")
|
|
|
|
|
assert html =~ ~s(data-mood="warm")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "CSS file structure" do
|
2026-02-18 01:15:28 +00:00
|
|
|
test "admin.css has .preview-frame variant selectors for theme editor" do
|
|
|
|
|
css_path = Path.join([File.cwd!(), "assets", "css", "admin.css"])
|
2026-01-17 16:28:22 +00:00
|
|
|
css_content = File.read!(css_path)
|
|
|
|
|
|
2026-01-17 21:43:26 +00:00
|
|
|
# Variant selectors are editor-only (.preview-frame) using CSS nesting
|
|
|
|
|
# The file uses &[data-*] syntax inside .preview-frame { }
|
2026-02-18 01:15:28 +00:00
|
|
|
# These rules are in admin.css (admin only), not shop.css
|
2026-01-17 21:43:26 +00:00
|
|
|
assert css_content =~ ".preview-frame"
|
|
|
|
|
assert css_content =~ "&[data-mood=\"dark\"]"
|
|
|
|
|
assert css_content =~ "&[data-mood=\"warm\"]"
|
|
|
|
|
assert css_content =~ "&[data-typography=\"modern\"]"
|
|
|
|
|
assert css_content =~ "&[data-shape=\"sharp\"]"
|
2026-01-17 16:28:22 +00:00
|
|
|
end
|
|
|
|
|
|
2026-01-17 21:43:26 +00:00
|
|
|
test "theme-layer2-attributes.css has shared .themed component styles" do
|
2026-01-17 16:28:22 +00:00
|
|
|
css_path = Path.join([File.cwd!(), "assets", "css", "theme-layer2-attributes.css"])
|
|
|
|
|
css_content = File.read!(css_path)
|
|
|
|
|
|
2026-01-17 21:43:26 +00:00
|
|
|
# Component styles use .themed for shared styling (both shop and preview)
|
|
|
|
|
assert css_content =~ ".themed"
|
|
|
|
|
# Uses CSS nesting syntax
|
|
|
|
|
assert css_content =~ "& .product-card"
|
|
|
|
|
assert css_content =~ "& .filter-pill"
|
2026-01-17 16:28:22 +00:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "generated CSS cache" do
|
2026-01-17 21:43:26 +00:00
|
|
|
test "generated CSS includes ALL theme token categories" do
|
|
|
|
|
# Apply a preset with specific values
|
|
|
|
|
{:ok, settings} = Settings.apply_preset(:night)
|
2026-01-17 16:28:22 +00:00
|
|
|
|
|
|
|
|
# Generate CSS
|
2026-02-18 21:23:15 +00:00
|
|
|
css = Berrypod.Theme.CSSGenerator.generate(settings)
|
2026-01-17 16:28:22 +00:00
|
|
|
|
2026-01-17 21:43:26 +00:00
|
|
|
# Mood tokens (surface, text, border colors)
|
|
|
|
|
assert css =~ "--t-surface-base:"
|
|
|
|
|
assert css =~ "--t-text-primary:"
|
|
|
|
|
assert css =~ "--t-border-default:"
|
|
|
|
|
|
|
|
|
|
# Typography tokens
|
|
|
|
|
assert css =~ "--t-font-heading:"
|
|
|
|
|
assert css =~ "--t-font-body:"
|
|
|
|
|
assert css =~ "--t-heading-weight:"
|
|
|
|
|
|
|
|
|
|
# Shape tokens (border radii)
|
|
|
|
|
assert css =~ "--t-radius-sm:"
|
|
|
|
|
assert css =~ "--t-radius-button:"
|
|
|
|
|
assert css =~ "--t-radius-card:"
|
|
|
|
|
|
|
|
|
|
# Density tokens
|
|
|
|
|
assert css =~ "--t-density:"
|
|
|
|
|
assert css =~ "--space-md:"
|
|
|
|
|
assert css =~ "--space-lg:"
|
|
|
|
|
|
|
|
|
|
# Slider-controlled values
|
2026-01-17 16:28:22 +00:00
|
|
|
assert css =~ "--t-accent-l:"
|
2026-02-20 23:53:42 +00:00
|
|
|
assert css =~ "--t-accent-c:"
|
|
|
|
|
assert css =~ "--t-accent-h:"
|
2026-01-17 16:28:22 +00:00
|
|
|
assert css =~ "--t-font-size-scale:"
|
|
|
|
|
assert css =~ "--t-heading-weight-override:"
|
|
|
|
|
end
|
|
|
|
|
|
2026-01-17 21:43:26 +00:00
|
|
|
test "generated CSS uses correct values for dark mood" do
|
|
|
|
|
{:ok, settings} = Settings.apply_preset(:night)
|
2026-02-18 21:23:15 +00:00
|
|
|
css = Berrypod.Theme.CSSGenerator.generate(settings)
|
2026-01-17 21:43:26 +00:00
|
|
|
|
|
|
|
|
# Dark mood should have dark surface colors
|
|
|
|
|
assert css =~ "--t-surface-base: #0a0a0a"
|
|
|
|
|
assert css =~ "--t-text-primary: #fafafa"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
test "generated CSS uses correct values for warm mood" do
|
|
|
|
|
{:ok, settings} = Settings.apply_preset(:gallery)
|
2026-02-18 21:23:15 +00:00
|
|
|
css = Berrypod.Theme.CSSGenerator.generate(settings)
|
2026-01-17 21:43:26 +00:00
|
|
|
|
|
|
|
|
# Warm mood should have warm surface colors
|
|
|
|
|
assert css =~ "--t-surface-base: #fdf8f3"
|
|
|
|
|
end
|
|
|
|
|
|
2026-01-17 16:28:22 +00:00
|
|
|
test "CSS cache is warmed on startup and invalidated on settings change" do
|
|
|
|
|
# Ensure cache has content
|
2026-02-18 21:23:15 +00:00
|
|
|
Berrypod.Theme.CSSCache.warm()
|
2026-01-17 16:28:22 +00:00
|
|
|
|
2026-02-18 21:23:15 +00:00
|
|
|
{:ok, css1} = Berrypod.Theme.CSSCache.get()
|
2026-01-17 16:28:22 +00:00
|
|
|
assert is_binary(css1)
|
|
|
|
|
assert css1 =~ "--t-accent-h:"
|
|
|
|
|
|
|
|
|
|
# Change settings (this should invalidate and rewarm cache)
|
|
|
|
|
{:ok, _settings} = Settings.apply_preset(:night)
|
|
|
|
|
|
2026-02-18 21:23:15 +00:00
|
|
|
{:ok, css2} = Berrypod.Theme.CSSCache.get()
|
2026-01-17 16:28:22 +00:00
|
|
|
assert is_binary(css2)
|
|
|
|
|
|
|
|
|
|
# The CSS should be different (different accent color)
|
|
|
|
|
# Note: this may or may not be true depending on preset colors
|
|
|
|
|
assert css2 =~ "--t-accent-h:"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|