defmodule SimpleshopTheme.Images.OptimizerTest do use SimpleshopTheme.DataCase, async: false alias SimpleshopTheme.Images.Optimizer import SimpleshopTheme.ImageFixtures setup do cleanup_cache() on_exit(&cleanup_cache/0) :ok end describe "applicable_widths/1" do test "returns all widths for large source" do assert [400, 800, 1200] = Optimizer.applicable_widths(1500) end test "excludes widths larger than source" do assert [400, 800] = Optimizer.applicable_widths(900) assert [400] = Optimizer.applicable_widths(500) end test "returns source width when smaller than minimum" do assert [300] = Optimizer.applicable_widths(300) assert [50] = Optimizer.applicable_widths(50) end end describe "to_optimized_webp/1" do test "converts image and returns dimensions" do {:ok, webp, width, height} = Optimizer.to_optimized_webp(sample_jpeg()) assert is_binary(webp) assert width == 1200 assert height == 800 # WebP magic bytes: RIFF....WEBP assert <<"RIFF", _size::binary-size(4), "WEBP", _::binary>> = webp end test "resizes images larger than 2000px" do # Create a large test image by scaling up {:ok, image} = Image.open("test/fixtures/sample_1200x800.jpg") {:ok, large} = Image.thumbnail(image, 3000) {:ok, large_data} = Image.write(large, :memory, suffix: ".jpg") {:ok, webp, width, height} = Optimizer.to_optimized_webp(large_data) assert is_binary(webp) assert width == 2000 # Height should be proportionally scaled assert height <= 2000 end test "returns error for invalid data" do assert {:error, _} = Optimizer.to_optimized_webp("not an image") end end describe "process_for_image/1" do test "generates AVIF and WebP variants for 1200px image" do image = image_fixture(%{source_width: 1200, source_height: 800}) assert {:ok, [400, 800, 1200]} = Optimizer.process_for_image(image.id) # Source WebP for Plug.Static serving assert File.exists?(Path.join(Optimizer.cache_dir(), "#{image.id}.webp")) for w <- [400, 800, 1200], fmt <- [:avif, :webp, :jpg] do assert File.exists?(cache_path(image.id, w, fmt)), "Missing #{w}.#{fmt}" end assert File.exists?(cache_path(image.id, "thumb", :jpg)) end test "generates only applicable widths for smaller image" do # Create fixture with smaller source width {:ok, webp, _w, _h} = Optimizer.to_optimized_webp(sample_jpeg()) image = %SimpleshopTheme.Media.Image{} |> SimpleshopTheme.Media.Image.changeset(%{ image_type: "product", filename: "small.jpg", content_type: "image/webp", file_size: byte_size(webp), data: webp, source_width: 600, source_height: 400, variants_status: "pending", is_svg: false }) |> Repo.insert!() assert {:ok, [400]} = Optimizer.process_for_image(image.id) assert File.exists?(cache_path(image.id, 400, :avif)) refute File.exists?(cache_path(image.id, 800, :avif)) end test "skips SVG images" do image = svg_fixture() assert {:ok, :svg_skipped} = Optimizer.process_for_image(image.id) end test "returns error for missing image" do assert {:error, :not_found} = Optimizer.process_for_image(Ecto.UUID.generate()) end test "is idempotent - skips existing files" do image = image_fixture(%{source_width: 1200, source_height: 800}) {:ok, _} = Optimizer.process_for_image(image.id) path = cache_path(image.id, 400, :avif) {:ok, %{mtime: mtime1}} = File.stat(path) Process.sleep(1100) {:ok, _} = Optimizer.process_for_image(image.id) {:ok, %{mtime: mtime2}} = File.stat(path) assert mtime1 == mtime2, "File was regenerated" end end describe "disk_variants_exist?/2" do test "returns true when all pre-generated variants exist" do image = image_fixture(%{source_width: 1200, source_height: 800}) {:ok, _} = Optimizer.process_for_image(image.id) # Should return true even without JPEG (only checks AVIF/WebP) assert Optimizer.disk_variants_exist?(image.id, 1200) end test "returns false when variants missing" do image = image_fixture(%{source_width: 1200, source_height: 800}) refute Optimizer.disk_variants_exist?(image.id, 1200) end end end