diff --git a/assets/js/interpreter.mjs b/assets/js/interpreter.mjs index d5dce58cf..68fb21bbd 100644 --- a/assets/js/interpreter.mjs +++ b/assets/js/interpreter.mjs @@ -117,6 +117,11 @@ export default class Interpreter { return `function ${moduleName}.${functionName}/${arity} is undefined (module ${moduleName} is not available)`; } + // callAnonymousFunction() has no unit tests in interpreter_test.mjs, only: + // * feature tests in test/features/test/function_calls/anonymous_function_test.exs, + // * consistency tests in test/elixir/hologram/ex_js_consistency/interpreter_test.exs (call anonymous function section). + // Unit test maintenance in interpreter_test.mjs would be problematic because tests would need to be updated + // each time Hologram.Compiler.Encoder's implementation changes. static callAnonymousFunction(fun, argsArray) { if (argsArray.length !== fun.arity) { Interpreter.raiseBadArityError(fun.arity, argsArray); diff --git a/test/javascript/interpreter_test.mjs b/test/javascript/interpreter_test.mjs index b2c68ec9d..25c0b60a9 100644 --- a/test/javascript/interpreter_test.mjs +++ b/test/javascript/interpreter_test.mjs @@ -225,308 +225,6 @@ describe("Interpreter", () => { }); }); - describe("callAnonymousFunction()", () => { - let anonFun, context; - - beforeEach(() => { - context = contextFixture({ - vars: {a: Type.integer(5), b: Type.integer(6), x: Type.integer(9)}, - }); - - // fn - // 1 -> :expr_1 - // 2 -> :expr_2 - // end - anonFun = Type.anonymousFunction( - 1, - [ - { - params: (_context) => [Type.integer(1)], - guards: [], - body: (_context) => { - return Type.atom("expr_1"); - }, - }, - { - params: (_context) => [Type.integer(2)], - guards: [], - body: (_context) => { - return Type.atom("expr_2"); - }, - }, - ], - context, - ); - }); - - it("runs the first matching clause", () => { - const result = Interpreter.callAnonymousFunction(anonFun, [ - Type.integer(1), - ]); - - assert.deepStrictEqual(result, Type.atom("expr_1")); - }); - - it("ignores not matching clauses", () => { - const result = Interpreter.callAnonymousFunction(anonFun, [ - Type.integer(2), - ]); - - assert.deepStrictEqual(result, Type.atom("expr_2")); - }); - - it("runs guards for each tried clause", () => { - // fn - // x when x == 1 -> :expr_1 - // y when y == 2 -> :expr_2 - // z when z == 3 -> :expr_3 - // end - const anonFun = Type.anonymousFunction( - 1, - [ - { - params: (_context) => [Type.variablePattern("x")], - guards: [ - (context) => Erlang["==/2"](context.vars.x, Type.integer(1)), - ], - body: (_context) => { - return Type.atom("expr_1"); - }, - }, - { - params: (_context) => [Type.variablePattern("y")], - guards: [ - (context) => Erlang["==/2"](context.vars.y, Type.integer(2)), - ], - body: (_context) => { - return Type.atom("expr_2"); - }, - }, - { - params: (_context) => [Type.variablePattern("z")], - guards: [ - (context) => Erlang["==/2"](context.vars.z, Type.integer(3)), - ], - body: (_context) => { - return Type.atom("expr_3"); - }, - }, - ], - context, - ); - - const result = Interpreter.callAnonymousFunction(anonFun, [ - Type.integer(3), - ]); - - assert.deepStrictEqual(result, Type.atom("expr_3")); - }); - - it("runs mutliple guards", () => { - // fn x when x == 1 when x == 2 -> x end - // - // fn x when :erlang.==(x, 1) when :erlang.==(x, 2) -> x end - const anonFun = Type.anonymousFunction( - 1, - [ - { - params: (_context) => [Type.variablePattern("x")], - guards: [ - (context) => Erlang["==/2"](context.vars.x, Type.integer(1)), - (context) => Erlang["==/2"](context.vars.x, Type.integer(2)), - ], - body: (context) => { - return context.vars.x; - }, - }, - ], - context, - ); - - const result1 = Interpreter.callAnonymousFunction(anonFun, [ - Type.integer(1), - ]); - - assert.deepStrictEqual(result1, Type.integer(1)); - - const result2 = Interpreter.callAnonymousFunction(anonFun, [ - Type.integer(2), - ]); - - assert.deepStrictEqual(result2, Type.integer(2)); - - assertBoxedError( - () => Interpreter.callAnonymousFunction(anonFun, [Type.integer(3)]), - "FunctionClauseError", - Interpreter.buildFunctionClauseErrorMsg("anonymous fn/1", [ - Type.integer(3), - ]), - ); - }); - - it("clones vars for each clause and has access to vars from closure", () => { - // x = 9 - // - // fn - // x, 1 when x == 1 -> :expr_1 - // y, 2 -> x - // end - const anonFun = Type.anonymousFunction( - 2, - [ - { - params: (_context) => [Type.variablePattern("x"), Type.integer(1)], - guards: [ - (context) => Erlang["==/2"](context.vars.x, Type.integer(1)), - ], - body: (_context) => { - return Type.atom("expr_1"); - }, - }, - { - params: (_context) => [Type.variablePattern("y"), Type.integer(2)], - guards: [], - body: (context) => { - return context.vars.x; - }, - }, - ], - context, - ); - - const result = Interpreter.callAnonymousFunction(anonFun, [ - Type.integer(2), - Type.integer(2), - ]); - - assert.deepStrictEqual(result, Type.integer(9)); - }); - - // Keep Elixir consistency test in sync: test/elixir/hologram/ex_js_consistency/interpreter_test.exs ("call anonymous function" section). - // TODO: client error message for this case is inconsistent with server error message - it("arity is invalid, called with no args", () => { - assertBoxedError( - () => Interpreter.callAnonymousFunction(anonFun, []), - "BadArityError", - "anonymous function with arity 1 called with no arguments", - ); - }); - - // Keep Elixir consistency test in sync: test/elixir/hologram/ex_js_consistency/interpreter_test.exs ("call anonymous function" section). - // TODO: client error message for this case is inconsistent with server error message - it("arity is invalid, called with a single arg", () => { - // fn - // 1, 2 -> :expr_1 - // 3, 4 -> :expr_2 - // end - anonFun = Type.anonymousFunction( - 2, - [ - { - params: (_context) => [Type.integer(1), Type.integer(2)], - guards: [], - body: (_context) => { - return Type.atom("expr_1"); - }, - }, - { - params: (_context) => [Type.integer(3), Type.integer(4)], - guards: [], - body: (_context) => { - return Type.atom("expr_2"); - }, - }, - ], - context, - ); - - assertBoxedError( - () => Interpreter.callAnonymousFunction(anonFun, [Type.integer(9)]), - "BadArityError", - "anonymous function with arity 2 called with 1 argument (9)", - ); - }); - - // Keep Elixir consistency test in sync: test/elixir/hologram/ex_js_consistency/interpreter_test.exs ("call anonymous function" section). - // TODO: client error message for this case is inconsistent with server error message - it("arity is invalid, called with multiple args", () => { - assertBoxedError( - () => - Interpreter.callAnonymousFunction(anonFun, [ - Type.integer(9), - Type.integer(8), - ]), - "BadArityError", - "anonymous function with arity 1 called with 2 arguments (9, 8)", - ); - }); - - // Keep Elixir consistency test in sync: test/elixir/hologram/ex_js_consistency/interpreter_test.exs ("call anonymous function" section). - // TODO: client error message for this case is inconsistent with server error message - it("arity is valid, but args don't match the pattern in any of the clauses", () => { - assertBoxedError( - () => Interpreter.callAnonymousFunction(anonFun, [Type.integer(9)]), - "FunctionClauseError", - Interpreter.buildFunctionClauseErrorMsg("anonymous fn/1", [ - Type.integer(9), - ]), - ); - }); - - it("has match operator in the clause pattern", () => { - // fn x = 1 = y -> x + y end - const anonFun = Type.anonymousFunction( - 1, - [ - { - params: (context) => [ - Interpreter.matchOperator( - Interpreter.matchOperator( - Type.variablePattern("y"), - Type.integer(1), - context, - ), - Type.variablePattern("x"), - context, - ), - ], - guards: [], - body: (context) => { - return Erlang["+/2"](context.vars.x, context.vars.y); - }, - }, - ], - context, - ); - - const result = Interpreter.callAnonymousFunction(anonFun, [ - Type.integer(1), - ]); - - assert.deepStrictEqual(result, Type.integer(2)); - }); - - it("errors raised inside function body are not caught", () => { - const anonFun = Type.anonymousFunction( - 0, - [ - { - params: (_context) => [], - guards: [], - body: (_context) => Interpreter.raiseArgumentError("my message"), - }, - ], - context, - ); - - assertBoxedError( - () => Interpreter.callAnonymousFunction(anonFun, []), - "ArgumentError", - "my message", - ); - }); - }); - describe("callNamedFunction()", () => { const alias = Type.alias( "Hologram.Test.Fixtures.ExJsConsistency.Interpreter.Module1",