defmodule CljElixir.Phase2Test do use ExUnit.Case, async: false # Helper to compile and evaluate CljElixir code # Uses vector_as_list: true until PersistentVector is implemented (Phase 3 WS-3) defp eval!(source) do case CljElixir.Compiler.eval_string(source, vector_as_list: true) do {:ok, result, _bindings} -> result {:error, errors} -> raise "CljElixir eval error: #{inspect(errors)}" end end # Protocols and core modules are compiled by the Mix compiler plugin # (compilers: [..., :clj_elixir] in mix.exs). No setup needed. # ========================================================================== # ILookup - get # ========================================================================== describe "get (ILookup)" do test "get from map with existing key" do assert eval!("(get {:a 1 :b 2} :a)") == 1 end test "get from map with missing key returns nil" do assert eval!("(get {:a 1} :b)") == nil end test "get from map with missing key and default" do assert eval!("(get {:a 1} :b 42)") == 42 end test "get from map with existing key ignores default" do assert eval!("(get {:a 1} :a 42)") == 1 end end # ========================================================================== # IAssociative - assoc, contains? # ========================================================================== describe "assoc (IAssociative)" do test "assoc adds new key to map" do assert eval!("(assoc {:a 1} :b 2)") == %{a: 1, b: 2} end test "assoc updates existing key" do assert eval!("(assoc {:a 1} :a 2)") == %{a: 2} end test "assoc on empty map" do assert eval!("(assoc {} :a 1)") == %{a: 1} end end describe "contains? (IAssociative)" do test "contains? returns true for existing key" do assert eval!("(contains? {:a 1 :b 2} :a)") == true end test "contains? returns false for missing key" do assert eval!("(contains? {:a 1} :c)") == false end end # ========================================================================== # IMap - dissoc # ========================================================================== describe "dissoc (IMap)" do test "dissoc removes key from map" do assert eval!("(dissoc {:a 1 :b 2} :a)") == %{b: 2} end test "dissoc with missing key returns same map" do assert eval!("(dissoc {:a 1} :b)") == %{a: 1} end end # ========================================================================== # ICounted - count # ========================================================================== describe "count (ICounted)" do test "count of map" do assert eval!("(count {:a 1 :b 2 :c 3})") == 3 end test "count of list" do assert eval!("(count (list 1 2 3))") == 3 end test "count of empty map" do assert eval!("(count {})") == 0 end test "count of tuple" do assert eval!("(count #el[1 2 3])") == 3 end test "count of string" do assert eval!("(count \"hello\")") == 5 end end # ========================================================================== # ISeq - first, rest # ========================================================================== describe "first/rest (ISeq)" do test "first of list" do assert eval!("(first (list 1 2 3))") == 1 end test "rest of list" do assert eval!("(rest (list 1 2 3))") == [2, 3] end test "first of empty list" do assert eval!("(first (list))") == nil end test "rest of empty list" do assert eval!("(rest (list))") == [] end end # ========================================================================== # ISeqable - seq # ========================================================================== describe "seq (ISeqable)" do test "seq of non-empty list returns the list" do assert eval!("(seq (list 1 2 3))") == [1, 2, 3] end test "seq of empty list returns nil" do assert eval!("(seq (list))") == nil end test "seq of map returns key-value pairs" do result = eval!("(seq {:a 1})") assert is_list(result) assert length(result) == 1 end end # ========================================================================== # ICollection - conj # ========================================================================== describe "conj (ICollection)" do test "conj onto list prepends" do assert eval!("(conj (list 2 3) 1)") == [1, 2, 3] end test "conj onto map merges tuple entry" do result = eval!("(conj {:a 1} {:b 2})") assert result == %{a: 1, b: 2} end end # ========================================================================== # IIndexed - nth # ========================================================================== describe "nth (IIndexed)" do test "nth from tuple" do assert eval!("(nth #el[10 20 30] 1)") == 20 end test "nth with default" do assert eval!("(nth #el[10 20] 5 :not-found)") == :"not-found" end end # ========================================================================== # IStack - peek, pop (via protocol on List) # ========================================================================== # Note: peek and pop are not in the builtin dispatch yet, they go through # the protocol directly - skip for now unless dispatch was added # ========================================================================== # Sequence wrapper functions # ========================================================================== describe "map (sequence function)" do test "map over list" do assert eval!("(map (fn [x] (inc x)) (list 1 2 3))") == [2, 3, 4] end end describe "filter" do test "filter list" do assert eval!("(filter (fn [x] (> x 2)) (list 1 2 3 4))") == [3, 4] end end describe "reduce" do test "reduce with initial value" do assert eval!("(reduce (fn [a b] (+ a b)) 0 (list 1 2 3))") == 6 end test "reduce without initial value" do assert eval!("(reduce (fn [a b] (+ a b)) (list 1 2 3))") == 6 end end describe "concat" do test "concat two lists" do assert eval!("(concat (list 1 2) (list 3 4))") == [1, 2, 3, 4] end end describe "take and drop" do test "take from list" do assert eval!("(take 2 (list 1 2 3 4))") == [1, 2] end test "drop from list" do assert eval!("(drop 2 (list 1 2 3 4))") == [3, 4] end end describe "sort" do test "sort list" do assert eval!("(sort (list 3 1 2))") == [1, 2, 3] end end describe "distinct" do test "distinct removes duplicates" do assert eval!("(distinct (list 1 2 1 3 2))") == [1, 2, 3] end end describe "frequencies" do test "frequencies counts occurrences" do assert eval!("(frequencies (list :a :b :a :c :b :a))") == %{a: 3, b: 2, c: 1} end end describe "partition" do test "partition into chunks" do assert eval!("(partition 2 (list 1 2 3 4))") == [[1, 2], [3, 4]] end end describe "mapcat" do test "mapcat flattens results" do assert eval!("(mapcat (fn [x] (list x x)) (list 1 2 3))") == [1, 1, 2, 2, 3, 3] end end # ========================================================================== # Map-specific functions # ========================================================================== describe "keys" do test "keys of map" do result = eval!("(keys {:a 1 :b 2})") assert Enum.sort(result) == [:a, :b] end end describe "vals" do test "vals of map" do result = eval!("(vals {:a 1 :b 2})") assert Enum.sort(result) == [1, 2] end end describe "merge" do test "merge two maps" do assert eval!("(merge {:a 1} {:b 2})") == %{a: 1, b: 2} end test "merge with overwrite" do assert eval!("(merge {:a 1} {:a 2})") == %{a: 2} end end describe "select-keys" do test "select-keys from map" do assert eval!("(select-keys {:a 1 :b 2 :c 3} (list :a :c))") == %{a: 1, c: 3} end end describe "into" do test "into map from list of tuples" do assert eval!("(into {} (list #el[:a 1] #el[:b 2]))") == %{a: 1, b: 2} end end # ========================================================================== # update # ========================================================================== describe "update" do test "update map value with function" do assert eval!("(update {:a 1} :a (fn [x] (inc x)))") == %{a: 2} end end # ========================================================================== # empty? # ========================================================================== describe "empty?" do test "empty? on empty map" do assert eval!("(empty? {})") == true end test "empty? on non-empty map" do assert eval!("(empty? {:a 1})") == false end end # ========================================================================== # Keyword-as-function through ILookup # ========================================================================== describe "keyword-as-function" do test "keyword as function on map" do assert eval!("(:name {:name \"Ada\"})") == "Ada" end test "keyword with default value" do assert eval!("(:age {:name \"Ada\"} 25)") == 25 end end # ========================================================================== # Compound functions (get-in, assoc-in, update-in) # ========================================================================== describe "get-in" do test "get-in nested map" do assert eval!("(get-in {:a {:b {:c 42}}} (list :a :b :c))") == 42 end test "get-in with missing key" do assert eval!("(get-in {:a {:b 1}} (list :a :c))") == nil end end describe "assoc-in" do test "assoc-in nested map" do assert eval!("(assoc-in {:a {:b 1}} (list :a :b) 2)") == %{a: %{b: 2}} end end describe "update-in" do test "update-in nested map" do assert eval!("(update-in {:a {:b 1}} (list :a :b) (fn [x] (inc x)))") == %{a: %{b: 2}} end end # ========================================================================== # reduce-kv # ========================================================================== describe "reduce-kv" do test "reduce-kv over map" do # Reduce a map collecting keys into a list result = eval!("(reduce-kv (fn [acc _k v] (+ acc v)) 0 {:a 1 :b 2 :c 3})") assert result == 6 end end # ========================================================================== # FFI ? and ! preservation # ========================================================================== describe "FFI name munging fix" do test "Map/has-key? works correctly" do assert eval!("(Map/has-key? {:a 1} :a)") == true end test "Map/has-key? returns false for missing key" do assert eval!("(Map/has-key? {:a 1} :b)") == false end end # ========================================================================== # End-to-end integration # ========================================================================== describe "end-to-end integration" do test "realistic data transformation pipeline" do source = """ (let [data (list {:name "Alice" :age 30} {:name "Bob" :age 25} {:name "Carol" :age 35})] (map (fn [p] (:name p)) (filter (fn [p] (> (get p :age) 28)) data))) """ assert eval!(source) == ["Alice", "Carol"] end test "nested map operations" do source = """ (let [m {:a 1 :b 2 :c 3}] (dissoc (assoc m :d 4) :b)) """ assert eval!(source) == %{a: 1, c: 3, d: 4} end test "count and empty? together" do source = """ (let [m {:a 1}] (list (count m) (empty? m) (empty? {}))) """ assert eval!(source) == [1, false, true] end end end