Skip to content

Commit

Permalink
Remove callAnonymousFunction() JS unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
bartblast committed Nov 13, 2024
1 parent ab07a1d commit cc04f38
Show file tree
Hide file tree
Showing 2 changed files with 5 additions and 302 deletions.
5 changes: 5 additions & 0 deletions assets/js/interpreter.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
302 changes: 0 additions & 302 deletions test/javascript/interpreter_test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit cc04f38

Please sign in to comment.