feat: add CSS generation system with custom properties and ETS cache
- Create theme-primitives.css with spacing, fonts, radius scales - Create theme-semantic.css with semantic CSS variable aliases - Update app.css to import theme CSS files - Add CSSGenerator module for dynamic CSS token generation - Generates mood variables (neutral, warm, cool, dark) - Generates typography variables (7 font combinations) - Generates shape variables (sharp, soft, round, pill) - Generates density variables (spacious, balanced, compact) - Converts hex colors to HSL for flexible manipulation - Add CSSCache GenServer with ETS table for performance - Caches generated CSS to avoid regeneration - Warms cache on application startup - Provides invalidation for theme updates - Add CSSCache to application supervision tree - Add comprehensive tests for CSS generation (29 tests) - Add comprehensive tests for preset validation (14 tests) - All tests passing (58 total tests, 0 failures) - Verified CSS generation and caching work correctly in IEx
This commit is contained in:
153
test/simpleshop_theme/theme/css_generator_test.exs
Normal file
153
test/simpleshop_theme/theme/css_generator_test.exs
Normal file
@@ -0,0 +1,153 @@
|
||||
defmodule SimpleshopTheme.Theme.CSSGeneratorTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias SimpleshopTheme.Theme.CSSGenerator
|
||||
alias SimpleshopTheme.Settings.ThemeSettings
|
||||
|
||||
describe "generate/1" do
|
||||
test "generates CSS for default theme settings" do
|
||||
settings = %ThemeSettings{}
|
||||
css = CSSGenerator.generate(settings)
|
||||
|
||||
assert is_binary(css)
|
||||
assert css =~ ".preview-frame, .shop-root"
|
||||
assert css =~ "--t-accent-h:"
|
||||
assert css =~ "--t-accent-s:"
|
||||
assert css =~ "--t-accent-l:"
|
||||
end
|
||||
|
||||
test "generates correct mood variables for neutral" do
|
||||
settings = %ThemeSettings{mood: "neutral"}
|
||||
css = CSSGenerator.generate(settings)
|
||||
|
||||
assert css =~ "--t-surface-base: #ffffff"
|
||||
assert css =~ "--t-text-primary: #171717"
|
||||
assert css =~ "--t-border-default: #e5e5e5"
|
||||
end
|
||||
|
||||
test "generates correct mood variables for dark" do
|
||||
settings = %ThemeSettings{mood: "dark"}
|
||||
css = CSSGenerator.generate(settings)
|
||||
|
||||
assert css =~ "--t-surface-base: #0a0a0a"
|
||||
assert css =~ "--t-text-primary: #fafafa"
|
||||
assert css =~ "--t-border-default: #262626"
|
||||
assert css =~ "--p-shadow-strength: 0.25"
|
||||
end
|
||||
|
||||
test "generates correct mood variables for warm" do
|
||||
settings = %ThemeSettings{mood: "warm"}
|
||||
css = CSSGenerator.generate(settings)
|
||||
|
||||
assert css =~ "--t-surface-base: #fdf8f3"
|
||||
assert css =~ "--t-text-primary: #1c1917"
|
||||
end
|
||||
|
||||
test "generates correct mood variables for cool" do
|
||||
settings = %ThemeSettings{mood: "cool"}
|
||||
css = CSSGenerator.generate(settings)
|
||||
|
||||
assert css =~ "--t-surface-base: #f4f7fb"
|
||||
assert css =~ "--t-text-primary: #0f172a"
|
||||
end
|
||||
|
||||
test "generates correct typography for clean" do
|
||||
settings = %ThemeSettings{typography: "clean"}
|
||||
css = CSSGenerator.generate(settings)
|
||||
|
||||
assert css =~ "--t-font-heading: var(--p-font-inter)"
|
||||
assert css =~ "--t-font-body: var(--p-font-inter)"
|
||||
assert css =~ "--t-heading-weight: 600"
|
||||
end
|
||||
|
||||
test "generates correct typography for editorial" do
|
||||
settings = %ThemeSettings{typography: "editorial"}
|
||||
css = CSSGenerator.generate(settings)
|
||||
|
||||
assert css =~ "--t-font-heading: var(--p-font-fraunces)"
|
||||
assert css =~ "--t-font-body: var(--p-font-source)"
|
||||
end
|
||||
|
||||
test "generates correct typography for modern" do
|
||||
settings = %ThemeSettings{typography: "modern"}
|
||||
css = CSSGenerator.generate(settings)
|
||||
|
||||
assert css =~ "--t-font-heading: var(--p-font-space)"
|
||||
assert css =~ "--t-heading-weight: 500"
|
||||
end
|
||||
|
||||
test "generates correct shape for sharp" do
|
||||
settings = %ThemeSettings{shape: "sharp"}
|
||||
css = CSSGenerator.generate(settings)
|
||||
|
||||
assert css =~ "--t-radius-button: 0"
|
||||
assert css =~ "--t-radius-card: 0"
|
||||
end
|
||||
|
||||
test "generates correct shape for soft" do
|
||||
settings = %ThemeSettings{shape: "soft"}
|
||||
css = CSSGenerator.generate(settings)
|
||||
|
||||
assert css =~ "--t-radius-button: var(--p-radius-md)"
|
||||
assert css =~ "--t-radius-card: var(--p-radius-lg)"
|
||||
end
|
||||
|
||||
test "generates correct shape for round" do
|
||||
settings = %ThemeSettings{shape: "round"}
|
||||
css = CSSGenerator.generate(settings)
|
||||
|
||||
assert css =~ "--t-radius-button: var(--p-radius-lg)"
|
||||
assert css =~ "--t-radius-card: var(--p-radius-xl)"
|
||||
end
|
||||
|
||||
test "generates correct shape for pill" do
|
||||
settings = %ThemeSettings{shape: "pill"}
|
||||
css = CSSGenerator.generate(settings)
|
||||
|
||||
assert css =~ "--t-radius-button: var(--p-radius-full)"
|
||||
end
|
||||
|
||||
test "generates correct density for spacious" do
|
||||
settings = %ThemeSettings{density: "spacious"}
|
||||
css = CSSGenerator.generate(settings)
|
||||
|
||||
assert css =~ "--t-density: 1.25"
|
||||
end
|
||||
|
||||
test "generates correct density for balanced" do
|
||||
settings = %ThemeSettings{density: "balanced"}
|
||||
css = CSSGenerator.generate(settings)
|
||||
|
||||
assert css =~ "--t-density: 1"
|
||||
end
|
||||
|
||||
test "generates correct density for compact" do
|
||||
settings = %ThemeSettings{density: "compact"}
|
||||
css = CSSGenerator.generate(settings)
|
||||
|
||||
assert css =~ "--t-density: 0.85"
|
||||
end
|
||||
|
||||
test "converts hex colors to HSL" do
|
||||
settings = %ThemeSettings{accent_color: "#ff0000"}
|
||||
css = CSSGenerator.generate(settings)
|
||||
|
||||
# Red should be H=0, S=100%, L=50%
|
||||
assert css =~ "--t-accent-h: 0"
|
||||
assert css =~ "--t-accent-s: 100%"
|
||||
assert css =~ "--t-accent-l: 50%"
|
||||
end
|
||||
|
||||
test "includes secondary colors" do
|
||||
settings = %ThemeSettings{
|
||||
secondary_accent_color: "#ea580c",
|
||||
sale_color: "#dc2626"
|
||||
}
|
||||
|
||||
css = CSSGenerator.generate(settings)
|
||||
|
||||
assert css =~ "--t-secondary-accent: #ea580c"
|
||||
assert css =~ "--t-sale-color: #dc2626"
|
||||
end
|
||||
end
|
||||
end
|
||||
133
test/simpleshop_theme/theme/presets_test.exs
Normal file
133
test/simpleshop_theme/theme/presets_test.exs
Normal file
@@ -0,0 +1,133 @@
|
||||
defmodule SimpleshopTheme.Theme.PresetsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias SimpleshopTheme.Theme.Presets
|
||||
|
||||
describe "all/0" do
|
||||
test "returns all 9 presets" do
|
||||
presets = Presets.all()
|
||||
|
||||
assert is_map(presets)
|
||||
assert map_size(presets) == 9
|
||||
assert Map.has_key?(presets, :gallery)
|
||||
assert Map.has_key?(presets, :studio)
|
||||
assert Map.has_key?(presets, :boutique)
|
||||
assert Map.has_key?(presets, :bold)
|
||||
assert Map.has_key?(presets, :playful)
|
||||
assert Map.has_key?(presets, :minimal)
|
||||
assert Map.has_key?(presets, :night)
|
||||
assert Map.has_key?(presets, :classic)
|
||||
assert Map.has_key?(presets, :impulse)
|
||||
end
|
||||
end
|
||||
|
||||
describe "get/1" do
|
||||
test "returns gallery preset with correct settings" do
|
||||
preset = Presets.get(:gallery)
|
||||
|
||||
assert preset.mood == "warm"
|
||||
assert preset.typography == "editorial"
|
||||
assert preset.shape == "soft"
|
||||
assert preset.density == "spacious"
|
||||
assert preset.grid_columns == "3"
|
||||
assert preset.header_layout == "centered"
|
||||
assert preset.accent_color == "#e85d04"
|
||||
end
|
||||
|
||||
test "returns studio preset with correct settings" do
|
||||
preset = Presets.get(:studio)
|
||||
|
||||
assert preset.mood == "neutral"
|
||||
assert preset.typography == "clean"
|
||||
assert preset.shape == "soft"
|
||||
assert preset.density == "balanced"
|
||||
assert preset.grid_columns == "4"
|
||||
assert preset.header_layout == "standard"
|
||||
assert preset.accent_color == "#3b82f6"
|
||||
end
|
||||
|
||||
test "returns boutique preset" do
|
||||
preset = Presets.get(:boutique)
|
||||
|
||||
assert preset.mood == "warm"
|
||||
assert preset.typography == "classic"
|
||||
assert preset.accent_color == "#b45309"
|
||||
end
|
||||
|
||||
test "returns bold preset" do
|
||||
preset = Presets.get(:bold)
|
||||
|
||||
assert preset.mood == "neutral"
|
||||
assert preset.typography == "modern"
|
||||
assert preset.shape == "sharp"
|
||||
assert preset.accent_color == "#dc2626"
|
||||
end
|
||||
|
||||
test "returns playful preset" do
|
||||
preset = Presets.get(:playful)
|
||||
|
||||
assert preset.typography == "friendly"
|
||||
assert preset.shape == "pill"
|
||||
assert preset.accent_color == "#8b5cf6"
|
||||
end
|
||||
|
||||
test "returns minimal preset" do
|
||||
preset = Presets.get(:minimal)
|
||||
|
||||
assert preset.mood == "cool"
|
||||
assert preset.typography == "minimal"
|
||||
assert preset.shape == "sharp"
|
||||
assert preset.accent_color == "#171717"
|
||||
end
|
||||
|
||||
test "returns night preset" do
|
||||
preset = Presets.get(:night)
|
||||
|
||||
assert preset.mood == "dark"
|
||||
assert preset.typography == "modern"
|
||||
assert preset.accent_color == "#f97316"
|
||||
end
|
||||
|
||||
test "returns classic preset" do
|
||||
preset = Presets.get(:classic)
|
||||
|
||||
assert preset.mood == "warm"
|
||||
assert preset.typography == "classic"
|
||||
assert preset.accent_color == "#166534"
|
||||
end
|
||||
|
||||
test "returns impulse preset with extended settings" do
|
||||
preset = Presets.get(:impulse)
|
||||
|
||||
assert preset.mood == "neutral"
|
||||
assert preset.typography == "impulse"
|
||||
assert preset.shape == "sharp"
|
||||
assert preset.accent_color == "#000000"
|
||||
assert preset.font_size == "medium"
|
||||
assert preset.heading_weight == "regular"
|
||||
assert preset.product_text_align == "center"
|
||||
end
|
||||
|
||||
test "returns nil for nonexistent preset" do
|
||||
assert Presets.get(:nonexistent) == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_names/0" do
|
||||
test "returns list of all preset names" do
|
||||
names = Presets.list_names()
|
||||
|
||||
assert is_list(names)
|
||||
assert length(names) == 9
|
||||
assert :gallery in names
|
||||
assert :studio in names
|
||||
assert :boutique in names
|
||||
assert :bold in names
|
||||
assert :playful in names
|
||||
assert :minimal in names
|
||||
assert :night in names
|
||||
assert :classic in names
|
||||
assert :impulse in names
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user