@nodeguy/generic
Version:
generic functions
266 lines (216 loc) • 6.29 kB
JavaScript
const assert = require(`@nodeguy/assert`);
const generic = require(`../lib`);
const _ = generic._;
describe("throws an exception when it doesn't match a pattern", function() {
it("has a specific message for single arguments", function() {
const futile = generic.function();
assert.throws(
() => futile("Why do I bother?"),
/Generic function: no method matches arguments \(Why do I bother/
);
});
it("has another message for multiple arguments", function() {
const futile = generic.function();
assert.throws(
() => futile("Why", "do", "I", "bother?"),
/Generic function: no method matches arguments \(Why, do, I, bother\?\)\./
);
});
});
it("includes the function's name in error messages", function() {
const futile = generic.function({ name: "futile" });
assert.throws(
() => futile("Why do I bother?"),
/Generic function 'futile': no method matches arguments \(Why do I bother/
);
});
it("matches the wildcard character", function() {
const identity = generic.function();
identity.method(_, x => x);
assert.equal(identity("Common Lisp"), "Common Lisp");
});
it("matches and returns values", function() {
const fibonacci = generic.function();
fibonacci.method([_], n => fibonacci(n - 1) + fibonacci(n - 2));
fibonacci.method([1], 1);
fibonacci.method([0], 0);
assert.equal(fibonacci(13), 233);
});
it("matches the most recently defined methods first", function() {
const orderMatters = generic.function();
orderMatters.method(_, false);
orderMatters.method(_, true);
assert(orderMatters());
});
describe("matches Regular Expressions", function() {
it("succeeds", function() {
const parseDate = generic.function();
parseDate.method([/(\d{4})-(\d{2})-(\d{2})/], ([, year, month, day]) => ({
year,
month,
day
}));
assert.deepEqual(parseDate("1995-05-23"), {
year: "1995",
month: "05",
day: "23"
});
});
it("fails with a non-string", function() {
const parseDate = generic.function();
parseDate.method(_, null);
parseDate.method([/(\d{4})-(\d{2})-(\d{2})/], ([, year, month, day]) => ({
year,
month,
day
}));
assert.deepEqual(parseDate(null), null);
});
it("fails with a non-matching string", function() {
const parseDate = generic.function();
parseDate.method(_, null);
parseDate.method([/(\d{4})-(\d{2})-(\d{2})/], ([, year, month, day]) => ({
year,
month,
day
}));
assert.deepEqual(parseDate("May 23, 1995"), null);
});
});
it("matches with a predicate function", function() {
const retort = generic.function();
retort.method(
args => args.length > 2,
() => "You are argumentative, aren't you?!"
);
assert.equal(
retort("and", "another", "thing"),
"You are argumentative, aren't you?!"
);
});
describe("matches objects", function() {
it("succeeds", function() {
const published = generic.function();
published.method(
[
{
ISBN: "1-55558-041-6"
}
],
({ year }) => year
);
assert.equal(
published({
title: "Common Lisp the Language, 2nd edition",
author: "Guy L. Steele",
year: 1990,
ISBN: "1-55558-041-6"
}),
1990
);
});
it("fails", function() {
const published = generic.function();
published.method(_, "unknown");
published.method(
[
{
title: "Common Lisp the Language, 2nd edition",
ISBN: "1-55558-041-6"
}
],
({ year }) => year
);
assert.equal(
published({
title: "Common Lisp the Language, 2nd edition"
}),
"unknown"
);
});
});
it("matches with nested predicate functions", function() {
const isArray = x => x instanceof Array;
const isNumber = x => typeof x === "number";
const sum = generic.function();
sum.method([isNumber, isNumber], (x, y) => x + y);
sum.method([isArray, isArray], (x, y) => x.concat(y));
assert.equal(sum(1, 2), 3);
assert.deepEqual(sum([1], [2]), [1, 2]);
});
it("matches deeply nested structures", function() {
const fullName = generic.function();
fullName.method(
[
"history",
{
name: _,
predecessor: {
name: _,
predecessor: {
name: "Lisp",
author: name => name.split(" ").length > 1
}
}
}
],
(type, lineage) => lineage.predecessor.predecessor.author
);
assert.equal(
fullName("history", {
name: "ANSI Common Lisp",
predecessor: {
name: "Common Lisp",
predecessor: {
name: "Lisp",
author: "John McCarthy"
}
}
}),
"John McCarthy"
);
assert.throws(
() =>
fullName("history", {
name: "ANSI Common Lisp",
predecessor: {
name: "Common Lisp",
predecessor: {
name: "Lisp",
author: "John"
}
}
}),
/Generic function: no method matches arguments \(history, \[object Object\]\)\./
);
});
it("optionally curries", function() {
const curried = generic.function({ curry: true });
curried.method([x => typeof x === "boolean"], "boolean");
curried.method([Array.isArray, Array.isArray], "arrays");
assert.equal(curried(true), "boolean");
const partial = curried([1]);
assert.equal(partial([2]), "arrays");
});
it("maps Arrays and Sets with mapping functions and objects", function() {
const _ = generic._;
const map = generic.function();
map.method([m => typeof m === "function", Array.isArray], (callback, array) =>
array.map(callback)
);
map.method(
[m => typeof m === "function", c => c instanceof Set],
(callback, set) => new Set([...set].map(callback))
);
map.method([m => typeof m === "object", _], (table, collection) =>
map(value => table[value], collection)
);
assert.deepEqual(map(Math.sqrt, [1, 4, 9]), [1, 2, 3]);
assert.deepEqual(map(Math.sqrt, new Set([1, 4, 9])), new Set([1, 2, 3]));
assert.deepEqual(map({ 1: 1, 4: 2, 9: 3 }, [1, 4, 9]), [1, 2, 3]);
assert.deepEqual(
map({ 1: 1, 4: 2, 9: 3 }, new Set([1, 4, 9])),
new Set([1, 2, 3])
);
});
;