feat: add Settings and Media contexts with theme settings schema

- Create settings table for site-wide key-value configuration
- Create images table for BLOB storage of logo/header images
- Add Setting schema with JSON/string/integer/boolean support
- Add ThemeSettings embedded schema with all theme options
- Add Settings context with get/put/update operations
- Add Media context for image uploads and retrieval
- Add Image schema with SVG detection and storage
- Add 9 curated theme presets (gallery, studio, boutique, etc.)
- Add comprehensive tests for Settings and Media contexts
- Add seeds with default Studio preset
- All tests passing (29 tests, 0 failures)
This commit is contained in:
2025-12-30 21:35:52 +00:00
parent bb4633895c
commit a401365943
13 changed files with 7508 additions and 0 deletions

View File

@@ -0,0 +1,113 @@
defmodule SimpleshopTheme.MediaTest do
use SimpleshopTheme.DataCase, async: true
alias SimpleshopTheme.Media
@valid_attrs %{
image_type: "logo",
filename: "logo.png",
content_type: "image/png",
file_size: 1024,
data: <<137, 80, 78, 71>>
}
@svg_attrs %{
image_type: "logo",
filename: "logo.svg",
content_type: "image/svg+xml",
file_size: 512,
data: "<svg xmlns=\"http://www.w3.org/2000/svg\"><circle r=\"10\"/></svg>"
}
describe "upload_image/1" do
test "uploads an image with valid attributes" do
assert {:ok, image} = Media.upload_image(@valid_attrs)
assert image.image_type == "logo"
assert image.filename == "logo.png"
assert image.content_type == "image/png"
assert image.is_svg == false
end
test "detects and stores SVG content" do
assert {:ok, image} = Media.upload_image(@svg_attrs)
assert image.is_svg == true
assert image.svg_content == @svg_attrs.data
end
test "validates required fields" do
assert {:error, changeset} = Media.upload_image(%{})
assert "can't be blank" in errors_on(changeset).image_type
assert "can't be blank" in errors_on(changeset).filename
end
test "validates image_type inclusion" do
attrs = Map.put(@valid_attrs, :image_type, "invalid")
assert {:error, changeset} = Media.upload_image(attrs)
assert "is invalid" in errors_on(changeset).image_type
end
test "validates file size" do
attrs = Map.put(@valid_attrs, :file_size, 10_000_000)
assert {:error, changeset} = Media.upload_image(attrs)
assert "must be less than 5000000" in errors_on(changeset).file_size
end
end
describe "get_image/1" do
test "returns image by id" do
{:ok, image} = Media.upload_image(@valid_attrs)
assert ^image = Media.get_image(image.id)
end
test "returns nil for nonexistent id" do
assert Media.get_image(Ecto.UUID.generate()) == nil
end
end
describe "get_logo/0 and get_header/0" do
test "get_logo returns a logo" do
{:ok, logo} = Media.upload_image(@valid_attrs)
result = Media.get_logo()
assert result.id == logo.id
assert result.image_type == "logo"
end
test "get_header returns most recent header" do
attrs = Map.put(@valid_attrs, :image_type, "header")
{:ok, header} = Media.upload_image(attrs)
result = Media.get_header()
assert result.id == header.id
end
test "get_logo returns nil when no logos exist" do
assert Media.get_logo() == nil
end
end
describe "delete_image/1" do
test "deletes an image" do
{:ok, image} = Media.upload_image(@valid_attrs)
assert {:ok, _} = Media.delete_image(image)
assert Media.get_image(image.id) == nil
end
end
describe "list_images_by_type/1" do
test "lists all images of a specific type" do
{:ok, logo1} = Media.upload_image(@valid_attrs)
{:ok, logo2} = Media.upload_image(@valid_attrs)
{:ok, _header} = Media.upload_image(Map.put(@valid_attrs, :image_type, "header"))
logos = Media.list_images_by_type("logo")
assert length(logos) == 2
assert Enum.any?(logos, &(&1.id == logo1.id))
assert Enum.any?(logos, &(&1.id == logo2.id))
end
test "returns empty list when no images of type exist" do
assert Media.list_images_by_type("product") == []
end
end
end

View File

@@ -0,0 +1,109 @@
defmodule SimpleshopTheme.SettingsTest do
use SimpleshopTheme.DataCase, async: true
alias SimpleshopTheme.Settings
alias SimpleshopTheme.Settings.ThemeSettings
describe "get_setting/2 and put_setting/3" do
test "stores and retrieves string settings" do
assert {:ok, _} = Settings.put_setting("site_name", "My Shop")
assert Settings.get_setting("site_name") == "My Shop"
end
test "stores and retrieves json settings" do
data = %{"foo" => "bar", "nested" => %{"key" => "value"}}
assert {:ok, _} = Settings.put_setting("custom_data", data, "json")
assert Settings.get_setting("custom_data") == data
end
test "stores and retrieves integer settings" do
assert {:ok, _} = Settings.put_setting("max_products", 100, "integer")
assert Settings.get_setting("max_products") == 100
end
test "stores and retrieves boolean settings" do
assert {:ok, _} = Settings.put_setting("feature_enabled", true, "boolean")
assert Settings.get_setting("feature_enabled") == true
end
test "returns default when setting doesn't exist" do
assert Settings.get_setting("nonexistent", "default") == "default"
assert Settings.get_setting("nonexistent") == nil
end
test "updates existing setting" do
assert {:ok, _} = Settings.put_setting("site_name", "Old Name")
assert {:ok, _} = Settings.put_setting("site_name", "New Name")
assert Settings.get_setting("site_name") == "New Name"
end
end
describe "get_theme_settings/0" do
test "returns default theme settings when none exist" do
settings = Settings.get_theme_settings()
assert %ThemeSettings{} = settings
assert settings.mood == "neutral"
assert settings.typography == "clean"
assert settings.shape == "soft"
assert settings.density == "balanced"
end
test "returns stored theme settings" do
{:ok, _} = Settings.update_theme_settings(%{mood: "dark", typography: "modern"})
settings = Settings.get_theme_settings()
assert settings.mood == "dark"
assert settings.typography == "modern"
end
end
describe "update_theme_settings/1" do
test "updates theme settings successfully" do
{:ok, settings} = Settings.update_theme_settings(%{mood: "warm", typography: "editorial"})
assert settings.mood == "warm"
assert settings.typography == "editorial"
end
test "validates mood values" do
{:error, changeset} = Settings.update_theme_settings(%{mood: "invalid"})
assert "is invalid" in errors_on(changeset).mood
end
test "validates typography values" do
{:error, changeset} = Settings.update_theme_settings(%{typography: "invalid"})
assert "is invalid" in errors_on(changeset).typography
end
test "validates logo size range" do
{:error, changeset} = Settings.update_theme_settings(%{logo_size: 10})
assert "must be greater than or equal to 24" in errors_on(changeset).logo_size
end
test "preserves existing settings when updating subset" do
{:ok, _} = Settings.update_theme_settings(%{mood: "warm"})
{:ok, settings} = Settings.update_theme_settings(%{typography: "modern"})
assert settings.mood == "warm"
assert settings.typography == "modern"
end
end
describe "apply_preset/1" do
test "applies gallery preset successfully" do
{:ok, settings} = Settings.apply_preset(:gallery)
assert settings.mood == "warm"
assert settings.typography == "editorial"
assert settings.shape == "soft"
assert settings.accent_color == "#e85d04"
end
test "applies studio preset successfully" do
{:ok, settings} = Settings.apply_preset(:studio)
assert settings.mood == "neutral"
assert settings.typography == "clean"
assert settings.accent_color == "#3b82f6"
end
test "returns error for invalid preset" do
assert {:error, :preset_not_found} = Settings.apply_preset(:nonexistent)
end
end
end