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:
113
test/simpleshop_theme/media_test.exs
Normal file
113
test/simpleshop_theme/media_test.exs
Normal 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
|
||||
109
test/simpleshop_theme/settings_test.exs
Normal file
109
test/simpleshop_theme/settings_test.exs
Normal 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
|
||||
Reference in New Issue
Block a user