255 lines
6.6 KiB
Elixir
255 lines
6.6 KiB
Elixir
defmodule CljElixir.REPLTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
alias CljElixir.REPL
|
|
|
|
describe "REPL.new" do
|
|
test "creates initial state" do
|
|
state = REPL.new()
|
|
assert state.bindings == []
|
|
assert state.history == []
|
|
assert state.counter == 1
|
|
end
|
|
end
|
|
|
|
describe "REPL.eval" do
|
|
test "evaluates simple expression" do
|
|
state = REPL.new()
|
|
assert {:ok, "3", _} = REPL.eval("(+ 1 2)", state)
|
|
end
|
|
|
|
test "pr-str formats output" do
|
|
state = REPL.new()
|
|
{:ok, result, _} = REPL.eval("\"hello\"", state)
|
|
assert result == "\"hello\""
|
|
end
|
|
|
|
test "nil result" do
|
|
state = REPL.new()
|
|
{:ok, result, _} = REPL.eval("nil", state)
|
|
assert result == "nil"
|
|
end
|
|
|
|
test "map result" do
|
|
state = REPL.new()
|
|
{:ok, result, _} = REPL.eval("{:a 1 :b 2}", state)
|
|
assert result =~ ":a"
|
|
assert result =~ "1"
|
|
end
|
|
|
|
test "increments counter" do
|
|
state = REPL.new()
|
|
{:ok, _, state2} = REPL.eval("1", state)
|
|
assert state2.counter == 2
|
|
{:ok, _, state3} = REPL.eval("2", state2)
|
|
assert state3.counter == 3
|
|
end
|
|
|
|
test "stores history" do
|
|
state = REPL.new()
|
|
{:ok, _, state2} = REPL.eval("(+ 1 2)", state)
|
|
assert state2.history == ["(+ 1 2)"]
|
|
{:ok, _, state3} = REPL.eval("(+ 3 4)", state2)
|
|
assert state3.history == ["(+ 3 4)", "(+ 1 2)"]
|
|
end
|
|
|
|
test "error returns error tuple" do
|
|
state = REPL.new()
|
|
{:error, msg, _} = REPL.eval("(defmodule REPLErrTest (invalid-syntax", state)
|
|
assert is_binary(msg)
|
|
assert msg =~ "Error" or msg =~ "error"
|
|
end
|
|
|
|
test "defmodule persists across evals" do
|
|
state = REPL.new()
|
|
|
|
{:ok, _, state2} =
|
|
REPL.eval("""
|
|
(defmodule REPLTestMod
|
|
(defn hello [] :hi))
|
|
""", state)
|
|
|
|
{:ok, result, _} = REPL.eval("(REPLTestMod/hello)", state2)
|
|
assert result == ":hi"
|
|
end
|
|
|
|
test "tuple result" do
|
|
state = REPL.new()
|
|
{:ok, result, _} = REPL.eval("#el[:ok 42]", state)
|
|
assert result == "#el[:ok 42]"
|
|
end
|
|
|
|
test "list result" do
|
|
state = REPL.new()
|
|
{:ok, result, _} = REPL.eval("(list 1 2 3)", state)
|
|
assert result == "(1 2 3)"
|
|
end
|
|
end
|
|
|
|
describe "ns tracking" do
|
|
test "ns sets current namespace" do
|
|
state = REPL.new()
|
|
assert REPL.current_ns(state) == "user"
|
|
|
|
{:ok, _, state2} = REPL.eval("(ns ReplNsTest1)", state)
|
|
assert state2.current_ns == "ReplNsTest1"
|
|
assert REPL.current_ns(state2) == "ReplNsTest1"
|
|
end
|
|
|
|
test "ns with defn creates module" do
|
|
state = REPL.new()
|
|
|
|
{:ok, _, state2} =
|
|
REPL.eval("""
|
|
(ns ReplNsTest2)
|
|
(defn greet [name] (str "hello " name))
|
|
""", state)
|
|
|
|
assert state2.current_ns == "ReplNsTest2"
|
|
{:ok, result, _} = REPL.eval("(ReplNsTest2/greet \"world\")", state2)
|
|
assert result == "\"hello world\""
|
|
end
|
|
|
|
test "bare defn in active ns updates module" do
|
|
state = REPL.new()
|
|
|
|
# Set up initial module
|
|
{:ok, _, state2} =
|
|
REPL.eval("""
|
|
(ns ReplNsTest3)
|
|
(defn hello [] :hi)
|
|
""", state)
|
|
|
|
# Add a new function without ns
|
|
{:ok, result, state3} = REPL.eval("(defn world [] :earth)", state2)
|
|
assert result == "#'ReplNsTest3/world"
|
|
|
|
# Both functions should work
|
|
{:ok, r1, _} = REPL.eval("(ReplNsTest3/hello)", state3)
|
|
assert r1 == ":hi"
|
|
{:ok, r2, _} = REPL.eval("(ReplNsTest3/world)", state3)
|
|
assert r2 == ":earth"
|
|
end
|
|
|
|
test "redefine function in active ns" do
|
|
state = REPL.new()
|
|
|
|
{:ok, _, state2} =
|
|
REPL.eval("""
|
|
(ns ReplNsTest4)
|
|
(defn greet [] "v1")
|
|
""", state)
|
|
|
|
{:ok, r1, _} = REPL.eval("(ReplNsTest4/greet)", state2)
|
|
assert r1 == "\"v1\""
|
|
|
|
# Redefine greet
|
|
{:ok, _, state3} = REPL.eval("(defn greet [] \"v2\")", state2)
|
|
|
|
{:ok, r2, _} = REPL.eval("(ReplNsTest4/greet)", state3)
|
|
assert r2 == "\"v2\""
|
|
end
|
|
|
|
test "bare defn without ns evals plain" do
|
|
state = REPL.new()
|
|
# No namespace set — def outside module should fail
|
|
{:error, _, _} = REPL.eval("(defn orphan [] :lost)", state)
|
|
end
|
|
|
|
test "expressions eval normally in ns context" do
|
|
state = REPL.new()
|
|
|
|
{:ok, _, state2} =
|
|
REPL.eval("""
|
|
(ns ReplNsTest5)
|
|
(defn add [a b] (+ a b))
|
|
""", state)
|
|
|
|
# Expression (no def) goes through plain eval
|
|
{:ok, result, _} = REPL.eval("(ReplNsTest5/add 3 4)", state2)
|
|
assert result == "7"
|
|
end
|
|
|
|
test "mixed defs and exprs in ns context" do
|
|
state = REPL.new()
|
|
|
|
{:ok, _, state2} =
|
|
REPL.eval("""
|
|
(ns ReplNsTest6)
|
|
(defn double [x] (* x 2))
|
|
""", state)
|
|
|
|
# Eval def + expression together
|
|
{:ok, result, state3} =
|
|
REPL.eval("""
|
|
(defn triple [x] (* x 3))
|
|
(ReplNsTest6/triple 5)
|
|
""", state2)
|
|
|
|
# Result should be the expression value
|
|
assert result == "15"
|
|
|
|
# Both functions should work
|
|
{:ok, r1, _} = REPL.eval("(ReplNsTest6/double 4)", state3)
|
|
assert r1 == "8"
|
|
end
|
|
|
|
test "switching namespace" do
|
|
state = REPL.new()
|
|
|
|
{:ok, _, state2} =
|
|
REPL.eval("""
|
|
(ns ReplNsTestA)
|
|
(defn a-fn [] :from-a)
|
|
""", state)
|
|
|
|
{:ok, _, state3} =
|
|
REPL.eval("""
|
|
(ns ReplNsTestB)
|
|
(defn b-fn [] :from-b)
|
|
""", state2)
|
|
|
|
assert state3.current_ns == "ReplNsTestB"
|
|
|
|
# Both modules still work
|
|
{:ok, r1, _} = REPL.eval("(ReplNsTestA/a-fn)", state3)
|
|
assert r1 == ":from-a"
|
|
{:ok, r2, _} = REPL.eval("(ReplNsTestB/b-fn)", state3)
|
|
assert r2 == ":from-b"
|
|
end
|
|
end
|
|
|
|
describe "REPL.balanced?" do
|
|
test "balanced parens" do
|
|
assert REPL.balanced?("(+ 1 2)")
|
|
assert REPL.balanced?("(defn foo [x] (+ x 1))")
|
|
assert REPL.balanced?("42")
|
|
assert REPL.balanced?("")
|
|
end
|
|
|
|
test "unbalanced parens" do
|
|
refute REPL.balanced?("(+ 1 2")
|
|
refute REPL.balanced?("(defn foo [x]")
|
|
refute REPL.balanced?("(let [x 1")
|
|
end
|
|
|
|
test "balanced with nested" do
|
|
assert REPL.balanced?("(let [x (+ 1 2)] (* x x))")
|
|
end
|
|
|
|
test "string contents not counted" do
|
|
assert REPL.balanced?("(str \"(hello)\")")
|
|
assert REPL.balanced?("(str \"[not real]\")")
|
|
end
|
|
|
|
test "comment contents not counted" do
|
|
assert REPL.balanced?("(+ 1 2) ; this has unbalanced (")
|
|
end
|
|
|
|
test "mixed delimiters" do
|
|
assert REPL.balanced?("(let [{:keys [a b]} {:a 1 :b 2}] (+ a b))")
|
|
refute REPL.balanced?("(let [{:keys [a b]} {:a 1 :b 2}] (+ a b)")
|
|
end
|
|
end
|
|
end
|