From 97e16188f45ccd2cb7a061ff88f3c5bd4cc8e9c4 Mon Sep 17 00:00:00 2001 From: dl-alexandre <166029845+dl-alexandre@users.noreply.github.com> Date: Thu, 21 May 2026 19:01:44 -0700 Subject: [PATCH] feat: support collocated screen templates --- lib/mob/screen.ex | 36 +++++++++++++++++++++++++++ test/mob/screen_collocation_test.exs | 37 ++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 test/mob/screen_collocation_test.exs diff --git a/lib/mob/screen.ex b/lib/mob/screen.ex index 853f18d..9ac85ae 100644 --- a/lib/mob/screen.ex +++ b/lib/mob/screen.ex @@ -118,10 +118,46 @@ defmodule Mob.Screen do "Add a handle_event/3 clause to handle it." end + @before_compile Mob.Screen defoverridable dump_state: 1, load_state: 2, handle_info: 2, terminate: 2, handle_event: 3 end end + defmacro __before_compile__(env) do + template = Path.rootname(env.file) <> ".mob.heex" + + cond do + Module.defines?(env.module, {:render, 1}) -> + quote(do: :ok) + + File.exists?(template) -> + source = + template + |> File.read!() + |> String.split("\n") + |> Enum.map_join("\n", &(" " <> &1)) + + render_ast = + Code.string_to_quoted!(""" + def render(assigns) do + import Mob.Sigil + + ~MOB\"\"\" + #{source} + \"\"\" + end + """) + + quote do + @external_resource unquote(template) + unquote(render_ast) + end + + true -> + quote(do: :ok) + end + end + # ── GenServer wrapper ───────────────────────────────────────────────────── use GenServer diff --git a/test/mob/screen_collocation_test.exs b/test/mob/screen_collocation_test.exs new file mode 100644 index 0000000..a3866f7 --- /dev/null +++ b/test/mob/screen_collocation_test.exs @@ -0,0 +1,37 @@ +defmodule Mob.ScreenCollocationTest do + use ExUnit.Case, async: true + + test "use Mob.Screen compiles a sibling .mob.heex template into render/1" do + dir = + Path.join(System.tmp_dir!(), "mob_screen_collocation_#{System.unique_integer([:positive])}") + + File.mkdir_p!(dir) + on_exit(fn -> File.rm_rf!(dir) end) + + module = Module.concat([:"CollocatedScreen#{System.unique_integer([:positive])}"]) + source = Path.join(dir, "collocated_screen.ex") + template = Path.join(dir, "collocated_screen.mob.heex") + + File.write!(template, """ + + + + """) + + File.write!(source, """ + defmodule #{inspect(module)} do + use Mob.Screen + + def mount(_params, _session, socket), do: {:ok, socket} + end + """) + + Code.compile_file(source) + + assert %{ + type: :column, + props: %{}, + children: [%{type: :text, props: %{text: "Hello"}, children: []}] + } = module.render(%{title: "Hello"}) + end +end