defmodule CljElixir.Phase7Test do use ExUnit.Case, async: false defp eval!(source) do case CljElixir.Compiler.eval_string(source) do {:ok, result, _} -> result {:error, errors} -> raise "Compilation failed: #{inspect(errors)}" end end defp compile!(source) do case CljElixir.Compiler.compile_to_beam(source) do {:ok, modules} -> modules {:error, errors} -> raise "Compilation failed: #{inspect(errors)}" end end describe "m/=> function specs" do test "simple function spec compiles" do compile!(""" (defmodule SpecTest1 (defn hello [name] (str "hello " name)) (m/=> hello [:=> [:cat :string] :string])) """) assert true after :code.purge(SpecTest1) :code.delete(SpecTest1) end test "multi-param function spec" do compile!(""" (defmodule SpecTest2 (defn add [a b] (+ a b)) (m/=> add [:=> [:cat :int :int] :int])) """) assert true after :code.purge(SpecTest2) :code.delete(SpecTest2) end test "optional param generates multiple specs" do compile!(""" (defmodule SpecTest3 (defn greet ([name] (greet name "hello")) ([name greeting] (str greeting " " name))) (m/=> greet [:=> [:cat :string [:? :string]] :string])) """) assert true after :code.purge(SpecTest3) :code.delete(SpecTest3) end test "multi-arity via :function" do compile!(""" (defmodule SpecTest4 (defn greet ([name] (greet name "hello")) ([name greeting] (str greeting " " name))) (m/=> greet [:function [:=> [:cat :string] :string] [:=> [:cat :string :string] :string]])) """) assert true after :code.purge(SpecTest4) :code.delete(SpecTest4) end test "spec with various types" do compile!(""" (defmodule SpecTest5 (defn process [x] x) (m/=> process [:=> [:cat :any] [:or :int :string :nil]])) """) assert true after :code.purge(SpecTest5) :code.delete(SpecTest5) end test "spec with map type" do compile!(""" (defmodule SpecTest6 (defn get-name [user] (:name user)) (m/=> get-name [:=> [:cat [:map [:name :string] [:age :int]]] :string])) """) assert true after :code.purge(SpecTest6) :code.delete(SpecTest6) end test "spec with tuple return" do compile!(""" (defmodule SpecTest7 (defn fetch [id] #el[:ok id]) (m/=> fetch [:=> [:cat :int] [:tuple :atom :int]])) """) assert true after :code.purge(SpecTest7) :code.delete(SpecTest7) end end describe "auto @type from def schemas" do test "def with map schema generates type" do compile!(""" (defmodule TypeTest1 (def User [:map [:name :string] [:age :int]]) (defn get-name [user] (:name user))) """) assert true after :code.purge(TypeTest1) :code.delete(TypeTest1) end test "def with or schema" do compile!(""" (defmodule TypeTest2 (def Status [:enum :active :inactive :pending]) (defn check [s] s)) """) assert true after :code.purge(TypeTest2) :code.delete(TypeTest2) end test "def with and schema" do compile!(""" (defmodule TypeTest3 (def PositiveInt [:and :int [:> 0]]) (defn check [n] n)) """) assert true after :code.purge(TypeTest3) :code.delete(TypeTest3) end end describe "schema cross-references" do test "spec references a named schema type" do compile!(""" (defmodule CrossRefTest1 (def User [:map [:name :string] [:age :int]]) (defn get-user [id] {:name "alice" :age 30}) (m/=> get-user [:=> [:cat :int] User])) """) assert true after :code.purge(CrossRefTest1) :code.delete(CrossRefTest1) end test "schema references another schema" do compile!(""" (defmodule CrossRefTest2 (def PositiveInt [:and :int [:> 0]]) (def Config [:map [:host :string] [:port PositiveInt] [:ssl? :boolean]]) (defn load-config [] {:host "localhost" :port 8080 :"ssl?" true})) """) assert true after :code.purge(CrossRefTest2) :code.delete(CrossRefTest2) end end describe "recursive schemas" do test "recursive schema with registry" do compile!(""" (defmodule RecursiveTest1 (def Tree [:schema {:registry {:tree [:or :int [:tuple [:ref :tree] [:ref :tree]]]}} [:ref :tree]]) (defn make-leaf [n] n)) """) assert true after :code.purge(RecursiveTest1) :code.delete(RecursiveTest1) end end describe "functions still work correctly" do test "module with spec can be called" do result = eval!(""" (defmodule FuncSpecTest1 (defn hello [name] (str "hello " name)) (m/=> hello [:=> [:cat :string] :string])) (FuncSpecTest1/hello "world") """) assert result == "hello world" after :code.purge(FuncSpecTest1) :code.delete(FuncSpecTest1) end test "module with type and spec" do result = eval!(""" (defmodule FuncSpecTest2 (def User [:map [:name :string] [:age :int]]) (defn make-user [name age] {:name name :age age}) (m/=> make-user [:=> [:cat :string :int] User])) (FuncSpecTest2/make-user "alice" 30) """) assert result == %{name: "alice", age: 30} after :code.purge(FuncSpecTest2) :code.delete(FuncSpecTest2) end end end