From 3fe8fa31541308cbfdfcf4f861d68f46b5ecc4f8 Mon Sep 17 00:00:00 2001 From: EuAndreh Date: Sun, 1 Jun 2025 13:12:58 -0300 Subject: Import existing code as-is --- Makefile | 97 ++++++++++ deps.mk | 0 mkdeps.sh | 4 + src/sjs.js | 304 ++++++++++++++++++++++++++++++ tests/sjs.js | 607 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1012 insertions(+) create mode 100644 Makefile create mode 100644 deps.mk create mode 100755 mkdeps.sh create mode 100644 src/sjs.js create mode 100644 tests/sjs.js diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c89a0a8 --- /dev/null +++ b/Makefile @@ -0,0 +1,97 @@ +.POSIX: +DATE = 1970-01-01 +VERSION = 0.1.0 +NAME = sjs +NAME_UC = $(NAME) +## Installation prefix. Defaults to "/usr". +PREFIX = /usr +BINDIR = $(PREFIX)/bin +LIBDIR = $(PREFIX)/lib +JSLIBDIR = $(LIBDIR)/javascript +INCLUDEDIR = $(PREFIX)/include +SRCDIR = $(PREFIX)/src/$(NAME) +SHAREDIR = $(PREFIX)/share +LOCALEDIR = $(SHAREDIR)/locale +MANDIR = $(SHAREDIR)/man +EXEC = ./ +## Where to store the installation. Empty by default. +DESTDIR = +LDLIBS = +JSIMPL = node + + + +.SUFFIXES: +.SUFFIXES: .js .js-check + + + +all: +include deps.mk + +sources = \ + src/$(NAME).js \ + + +derived-assets = \ + +side-assets = \ + + + +## Default target. Builds all artifacts required for testing +## and installation. +all: $(derived-assets) + + + +tests/$(NAME).js-check: + $(JSIMPL) $*.js + +check-unit: tests/$(NAME).js-check + + +integration-tests = \ + +.PRECIOUS: $(integration-tests) +$(integration-tests): ALWAYS + sh $@ + +check-integration: $(integration-tests) + + +## Run all tests. Each test suite is isolated, so that a parallel +## build can run tests at the same time. The required artifacts +## are created if missing. +check: check-unit check-integration + + + +## Remove *all* derived artifacts produced during the build. +## A dedicated test asserts that this is always true. +clean: + rm -rf $(derived-assets) $(side-assets) + + +## Installs into $(DESTDIR)$(PREFIX). Its dependency target +## ensures that all installable artifacts are crafted beforehand. +install: all + mkdir -p \ + '$(DESTDIR)$(BINDIR)' \ + '$(DESTDIR)$(JSLIBDIR)' \ + '$(DESTDIR)$(SRCDIR)' \ + + cp src/$(NAME).js '$(DESTDIR)$(JSLIBDIR)' + cp $(sources) '$(DESTDIR)$(SRCDIR)' + +## Uninstalls from $(DESTDIR)$(PREFIX). This is a perfect mirror +## of the "install" target, and removes *all* that was installed. +## A dedicated test asserts that this is always true. +uninstall: + rm -rf \ + '$(DESTDIR)$(JSLIBDIR)'/$(NAME).js \ + '$(DESTDIR)$(SRCDIR)' \ + + + +ALWAYS: diff --git a/deps.mk b/deps.mk new file mode 100644 index 0000000..e69de29 diff --git a/mkdeps.sh b/mkdeps.sh new file mode 100755 index 0000000..e5606ff --- /dev/null +++ b/mkdeps.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -eu + +export LANG=POSIX.UTF-8 diff --git a/src/sjs.js b/src/sjs.js new file mode 100644 index 0000000..799f964 --- /dev/null +++ b/src/sjs.js @@ -0,0 +1,304 @@ +export const getIn = (obj, path) => + path.length === 0 + ? obj + : getIn(obj?.[path[0]], path.slice(1)); + +export const merge = (lhs, rhs) => { + if (lhs === undefined) { + return rhs; + } + + if (rhs === undefined) { + return lfs; + } + + if (typeof lhs !== "object") { + return rhs; + } + + const lkeys = Object.keys(lhs); + const rkeys = Object.keys(rhs); + const allKeys = lkeys.concat(rkeys); + return [...new Set(allKeys)].reduce( + (o, key) => ({ ...o, [key]: merge(lhs[key], rhs[key]) }), + {}, + ); +}; + +export const compareObject = (lhs, rhs) => { + const lstr = Object.entries(lhs).toSorted(); + const rstr = Object.entries(rhs).toSorted(); + + if (lstr < rstr) { + return -1; + } + + if (lstr > rstr) { + return 1; + } + + throw new Error("unreachable"); +}; + +const FROM = /[&<>'"]/g; + +const ESCAPES = { + '&': '&', + '<': '<', + '>': '>', + "'": ''', + '"': '"' +}; + +const mappingFn = c => ESCAPES[c]; + +export const escape = s => + String.prototype.replace.call(s, FROM, mappingFn); + +export const eq = (a, b) => { + if (a === b) { + return true; + } + + if (a === null || b === null) { + return false; + } + + if (typeof a != "object" || typeof b != "object") { + return false; + } + + if (Array.isArray(a) !== Array.isArray(b)) { + return false; + } + + if (Object.keys(a).length !== Object.keys(b).length) { + return false; + } + + for (const k in a) { + if (!b.hasOwnProperty(k)) { + return false; + } + if (!eq(a[k], b[k])) { + return false; + } + } + return true; +}; + +export const keys = (ks, obj) => + ks.reduce( + (ret, k) => + obj.hasOwnProperty(k) ? {...ret, [k]: obj[k]} : ret, + {}, + ); + +export const difference = (a, b) => { + const diff = new Set(a); + for (const el of b) { + diff.delete(el); + } + return diff; +}; + +export const assocIn = (obj, path, value) => + path.length === 0 ? obj : + path.length === 1 ? { ...obj, [path[0]]: value } : + { + ...obj, + [path[0]]: assocIn( + (obj[path[0]] || {}), + path.slice(1), + value + ) + }; + +export const dissoc = (obj, key) => { + const ret = { ...obj }; + delete ret[key] + return ret; +}; + +export const findFirst = (arr, fn) => { + for (const x of arr) { + const ret = fn(x); + if (ret) { + return ret; + } + } + + return null; +}; + +export const partial = (fn, ...startArgs) => + (...endArgs) => + fn(...startArgs, ...endArgs); + +export const strSortFn = (a, b) => a.localeCompare(b, "POSIX"); + +export const undefinedAsNull = x => x === undefined ? null : x; + +export const first = a => a[0]; +export const rest = a => a.slice(1); +export const butlast = a => a.slice(a, a.length - 1); +export const last = a => a[a.length - 1]; + +export const take = function*(n, gen) { + let i = 0n; + for (const x of gen) { + if (i >= n) { + break; + } + i++; + yield x; + } +}; + +export const range = function*(x, y, step = 1n) { + if (x === undefined) { + let i = 0n; + while (true) { + yield i++; + } + } + const [from, to] = y === undefined ? + [0n, x] : + [x, y]; + const fromn = BigInt(from); + const ton = BigInt(to); + const stepn = BigInt(step); + if (stepn === 0n) { + while (true) { + yield fromn; + } + } + if (step < 0n) { + for (let i = fromn; i > ton; i+= stepn) { + yield i; + } + } else { + for (let i = fromn; i < ton; i += stepn) { + yield i; + } + } +}; + +const t = ({ colors, err, assertEq }) => ({ + assertEq, + tap: x => { + err(`tap: ${x}\n`); + return x; + }, + start: msg => { + err(`${msg}:\n`); + }, + testing: async (msg, fn) => { + err(`${colors.yellow("testing")}: ${msg}... `); + try { + await fn(); + } catch (e) { + err(`${colors.red("ERR")}.\n`); + throw e; + } + err(`${colors.green("OK")}.\n`); + }, +}); + +class UnimplementedError extends Error {} + +export const getJSImpl = () => { + if (typeof scriptArgs !== "undefined") { + return "qjs"; + } + if (typeof Deno !== "undefined") { + return "deno"; + } + if (typeof process !== "undefined") { + return "node"; + } + // FIXME: add browser and bun + return "unknown"; +}; + +export const JSImpl = getJSImpl(); + +const mappings = { + ARGV: { + qjs: () => scriptArgs, + node: () => process.argv.slice(1), + deno: () => Deno.mainModule.substring("file://".length), + unknown: () => { + throw new UnimplementedError(`ARGV["${JSImpl}"]`); + }, + }, + exit: { + qjs: async () => { + const { exit } = await import("std"); + return exit; + }, + node: async () => process.exit, + deno: async () => Deno.exit, + unknown: () => { + throw new UnimplementedError(`exit["${JSImpl}"]`); + }, + }, + tconf: { + qjs: async () => await mappings.testConf.unknown(), + deno: async () => await mappings.testConf.node(), + browser: async () => { + class DOMAssertionError extends Error {} + + const w = s => document.querySelector("#test-report").innerHTML += s; + + const red = s => `${s}`; + const green = s => `${s}`; + const yellow = s => `${s}`; + return { + testStart: msg => w(`${msg}:\n`), + testing: msg => w(`${yellow("testing")}: ${msg}...`), + testOK: () => w(` ${green("OK")}.\n`), + assert: (x, msg = "") => { + if (!x) { + w(` ${red("ERROR")}.\n`); + w("\nSee console for details.\n"); + w("If possible, do report them."); + throw new DOMAssertionError(msg); + } + }, + }; + }, + + node: async () => { + const assert = await import("node:assert/strict"); + const process = await import("node:process"); + + const err = x => process.stderr.write(x); + const red = s => `\x1b[31m${s}\x1b[0m`; + const green = s => `\x1b[32m${s}\x1b[0m`; + const yellow = s => `\x1b[33m${s}\x1b[0m`; + + return { + err, + assertEq: assert.deepEqual, + colors: { + red, + green, + yellow, + }, + }; + }, + unknown: () => { throw new UnimplementedError(`exit["${JSImpl}"]`); }, + }, +}; + +export const exit = await mappings.exit[ JSImpl](); +export const ARGV = await mappings.ARGV[ JSImpl](); +export const tconf = await mappings.tconf[JSImpl](); + +export const runTests = async tests => { + const tFinal = t(tconf); + for (const testFn of tests) { + await testFn(tFinal); + } +}; diff --git a/tests/sjs.js b/tests/sjs.js new file mode 100644 index 0000000..bf5f3dd --- /dev/null +++ b/tests/sjs.js @@ -0,0 +1,607 @@ +import { + getIn, + merge, + compareObject, + escape, + eq, + keys, + difference, + assocIn, + dissoc, + findFirst, + partial, + strSortFn, + undefinedAsNull, + first, + rest, + butlast, + last, + take, + range, + runTests, +} from "../src/sjs.js"; + + + +const test_getIn = async t => { + t.start("getIn()"); + + await t.testing("empty values", () => { + t.assertEq(getIn({}, []), {}); + t.assertEq(getIn({ k: "v" }, []), { k: "v" }); + t.assertEq(getIn({}, [0]), undefined); + }); + + await t.testing("error when getting bad path", () => { + }); + + await t.testing("mix of arrays and objects", () => { + }); + + await t.testing("missing values", () => { + t.assertEq(getIn({}, ["a", "b", "c"]), undefined); + t.assertEq(getIn({ a: {}}, ["a", "b", "c"]), undefined); + t.assertEq(getIn({ a: { b: {}}}, ["a", "b", "c"]), undefined); + t.assertEq(getIn({ a: { b: {}, c: {}}}, ["a", "b", "c"]), undefined); + }); + + await t.testing("nested valeues", () => { + t.assertEq(getIn({ a: { b: { c: { d: "e" }}}}, ["a", "b", "c", "d"]), "e"); + }); +}; + +const test_merge = async t => { + t.start("merge()"); + + await t.testing("empty values", () => { + }); + + await t.testing("rhs gets preference over lhs", () => { + }); + + await t.testing("deep merge", () => { + }); +}; + +const test_compareObject = async t => { + t.start("compareObject()"); + + await t.testing("", () => {}); +}; + +const test_escape = async t => { + t.start("escape()"); + await t.testing("", () => {}); +}; + +const test_eq = t => { + t.start("eq()"); + t.testing("scalar values equality", () => { + t.assertEq(eq(0, 0), true); + t.assertEq(eq(100, 100), true); + t.assertEq(eq(1.5, 1.5), true); + t.assertEq(eq(-9, -9), true); + + t.assertEq(eq(0, 1), false); + t.assertEq(eq(100, 99), false); + t.assertEq(eq(1.5, 1.4), false); + t.assertEq(eq(-9, 9), false); + + + t.assertEq(eq(null, null), true); + t.assertEq(eq(undefined, undefined), true); + t.assertEq(eq("", ""), true); + t.assertEq(eq("a string", "a string"), true); + + t.assertEq(eq(null, undefined), false); + t.assertEq(eq(undefined, 0), false); + t.assertEq(eq("", "0"), false); + t.assertEq(eq("a string", "another string"), false); + + + t.assertEq(eq(true, true), true); + t.assertEq(eq(false, false), true); + + t.assertEq(eq(true, false), false); + + + t.assertEq(eq(1n, 1n), true); + t.assertEq(eq(99999999999999n, 99999999999999n), true); + + // t.assertEq(eq(1, 1n), true); + }); + + t.testing("array equality", () => { + t.assertEq(eq([], []), true); + + + t.assertEq(eq([0, 1, 2], [0, 1, 2]), true); + t.assertEq(eq([0, 1, 2], new Array(0, 1, 2)), true); + + t.assertEq(eq([0, 1, 2], [0, 1]), false); + t.assertEq(eq([0, 1], new Array(0, 1, 2)), false); + + + t.assertEq(eq([undefined], [undefined]), true); + t.assertEq(eq([null, 0, "", true], [null, 0, "", true]), true); + + + t.assertEq(eq([[[[0]]]], [[[[0]]]]), true); + + t.assertEq(eq([[[[0]]]], [0]), false); + }); + + t.testing("object equality", () => { + t.assertEq(eq({}, {}), true); + t.assertEq(eq({ a: 1 }, { a: 1 }), true); + + t.assertEq(eq({ a: 1, b: undefined }, { a: 1 }), false); + + + t.assertEq(eq( + { a: 1, b: { c: { d: "e" } } }, + { a: 1, b: { c: { d: "e" } } }, + ), true); + + class C {} + // ... + }); + + t.testing("mixed values", () => { + t.assertEq(eq( + {a: ["", 1, 2, 3, [{ b: { c: [ "d", "e" ]}}]]}, + {a: ["", 1, 2, 3, [{ b: { c: [ "d", "e" ]}}]]}, + ), true); + + t.assertEq(eq(null, {}), false); + + t.assertEq(eq([], {}), false); + + + t.assertEq(eq(new Date(123), new Date(123)), true); + t.assertEq(eq({ d: new Date(123) }, { d: new Date(123) }), true); + + // FIXME + // t.assertEq(!eq(new Date(123), new Date(321))); + // t.assertEq(!eq({ d: new Date(123) }, { d: new Date(321) })); + }); +}; + +const test_keys = async t => { + t.start("keys()"); + await t.testing("happy paths", () => { + t.assertEq( + { a: 1, b: 2 }, + keys(["a", "b"], { a: 1, b: 2, c: 3 }), + ); + }); + + await t.testing("stress scenarios", () => { + t.assertEq( + {}, + keys([], {}), + "empty selection of empty object", + ); + + t.assertEq( + {}, + keys([], {a: 1}), + "empty selection of non-empty object", + ); + + t.assertEq( + {}, + keys(["a"], {}), + "non-empty selection of empty object", + ); + + t.assertEq( + { a: undefined, b: null }, + keys(["a", "b", "c"], { a: undefined, b: null }), + "falsy values", + ); + }); +}; + +const test_difference = async t => { + t.start("difference()"); + + await t.testing("empty values", () => { + t.assertEq( + difference(new Set(), new Set()), + new Set(), + ); + + t.assertEq( + difference(new Set(), new Set([1, 2])), + new Set(), + ); + + t.assertEq( + difference(new Set([1, 2]), new Set()), + new Set([1, 2]), + ); + }); + + await t.testing("different subsets", () => { + t.assertEq( + difference(new Set([1, 2]), new Set([3, 4])), + new Set([1, 2]), + ); + + t.assertEq( + difference(new Set([1, 2, 3]), new Set([2, 4, 5])), + new Set([1, 3]), + ); + + t.assertEq( + difference(new Set([1]), new Set([1, 2, 3, 4, 5])), + new Set(), + ); + + t.assertEq( + difference(new Set([1, 2, 3]), new Set([1, 2, 3])), + new Set(), + ); + }); +}; + +const test_assocIn = async t => { + t.start("assocIn()"); + + await t.testing("empty values", () => { + t.assertEq(assocIn({}, [], null), {}); + t.assertEq(assocIn({ k: "v" }, [], null), { k: "v" }); + }); + + await t.testing("adding values", () => { + t.assertEq(assocIn({}, ["k"], "v"), { k: "v" }); + t.assertEq(assocIn({}, ["k1", "k2"], "v"), { k1: { k2: "v" }}); + t.assertEq(assocIn({}, ["k1", "k2", "k3"], "v"), { k1: { k2: { k3: "v" }}}); + t.assertEq(assocIn({ k: "v" }, ["k1", "k2"], "v"), { k: "v", k1: { k2: "v" }}); + }); + + await t.testing("replacing values", () => { + t.assertEq( + assocIn( + { k1: { k2: { k3: "before" }}}, + ["k1", "k2", "k3"], + "after" + ), + { k1: { k2: { k3: "after" }}} + ); + }); +}; + +const test_dissoc = async t => { + t.start("dissoc()"); + + await t.testing("empty values", () => { + t.assertEq(dissoc({}, "k"), {}); + }); + + await t.testing("noop when key does not exist", () => { + t.assertEq(dissoc({ a: 1 }, "b"), { a: 1 }); + }); + + await t.testing("removes the key", () => { + t.assertEq(dissoc({ a: 1, b: 2}, "b"), { a: 1 }); + }); +}; + +const test_findFirst = async t => { + t.start("findFirst()"); + + await t.testing("empty values", () => { + t.assertEq(findFirst([], () => {}), null); + }); + + await t.testing("when function doesn't transform, it behaves similarly to [].find()", () => { + const arr1 = [ 0, null, undefined, "", 1, 2 ]; + t.assertEq(findFirst(arr1, x => x), 1); + t.assertEq(arr1.find(x => x), 1); + + const arr2 = [ 0, null, undefined, "", false ]; + t.assertEq(findFirst(arr2, x => x), null); + t.assertEq(arr2.find(x => x), undefined); + }); + + await t.testing("when it does transform, we return the transformed value", () => { + const arr = [ 1, 3, 5, 6 ]; + + t.assertEq( + findFirst(arr, x => x % 2 === 0 && "a brand new value"), + "a brand new value", + ); + }); +}; + +const test_partial = async t => { + t.start("partial()"); + + await t.testing("empty values", () => { + const adder = (a, b, c) => a + b + c; + + const adder1 = partial(adder); + t.assertEq(adder1(1, 2, 3), 6); + + const adder2 = partial(adder, 4, 5, 6); + t.assertEq(adder2(), 15); + + const noargs = () => "static"; + t.assertEq(partial(noargs)(), noargs()); + }); + + await t.testing("too few arguments", () => { + const threeArgs = (a, b, c) => { + assert.notEqual(c, undefined); + return a + b + c; + }; + + const add1 = partial(threeArgs, 1); + return; // FIXME + assert.throws( + () => add1(2), + assert.AssertionError, + ); + + const add1And2 = partial(threeArgs, 1, 2); + assert.throws( + () => add1And2(), + assert.AssertionError, + ); + + const addNothing = partial(threeArgs); + assert.throws( + () => addNothing(), + assert.AssertionError, + ); + }); + + await t.testing("too many arguments", () => { + const twoArgs = (a, b) => a + b; + + t.assertEq(partial(twoArgs, 1)(2, 3), 3); + t.assertEq(partial(twoArgs, 1, 2)(3), 3); + }); + + await t.testing("daily usage", () => { + const twoArg = (a, b) => a + b; + + const numbers = [ 1, 2, 3, 4, 5 ]; + t.assertEq( + numbers.map(partial(twoArg, 2)), + [ 3, 4, 5, 6, 7 ], + ); + }); + + await t.testing("nested partials", () => { + const threeArgs = (a, b, c) => a + b + c; + + const add1 = partial(threeArgs, 1); + const add1And2 = partial(add1, 2); + + t.assertEq(add1And2(3), 6); + }); +}; + +const test_strSortFn = async t => { + t.start("strSortFn()"); + + await t.testing("empty value", () => { + t.assertEq(strSortFn("", ""), 0); + }); + + await t.testing("default sort", () => { + const arr = [ "a", "Z" ]; + t.assertEq( + [...arr].sort(strSortFn), + [...arr].sort().reverse(), + ); + }); +}; + +const test_undefinedAsNull = async t => { + t.start("undefinedAsNull()"); + + await t.testing("null for undefined or null", () => { + t.assertEq(undefinedAsNull(undefined), null); + t.assertEq(undefinedAsNull(null), null); + }); + + await t.testing("identity otherwise", () => { + const expected = [ + " ", "", 0, 1, -1.1, true, false, + [], [ "" ], {}, { k: "v" }, + ]; + const given = expected.map(undefinedAsNull); + t.assertEq(given, expected); + }); +}; + +const test_first = async t => { + t.start("first()"); + + await t.testing("undefined for an empty array", () => { + t.assertEq(undefined, first([])); + t.assertEq(undefined, first("")); + }); + + await t.testing("the first element otherwise", () => { + t.assertEq(1, first([1, 2, 3])); + t.assertEq("a", first("abc")); + }); +}; + +const test_rest = async t => { + t.start("rest()"); + + await t.testing("an empty array when no more elements are available", () => { + t.assertEq([], rest([])); + t.assertEq([], rest([1])); + t.assertEq("", rest("")); + t.assertEq("bc", rest("abc")); + }); + + await t.testing("the rest of the collection otherwise", () => { + t.assertEq([2, 3], rest([1, 2, 3])); + t.assertEq("bc", rest("abc")); + }); + + await t.testing("combines with first() well", () => { + const arr = ["anything", "can", "go", "here"]; + t.assertEq(arr, [ first(arr), ...rest(arr) ]); + }); +}; + +const test_butlast = async t => { + t.start("butlast()"); + + await t.testing("empty array when ther are no more elements", () => { + t.assertEq([], butlast([])); + t.assertEq([], butlast([1])); + t.assertEq("", butlast("")); + t.assertEq("", butlast("c")); + }); + + await t.testing("the beginning of the array otherwise", () => { + t.assertEq([1, 2], butlast([1, 2, 3])); + t.assertEq("ab", butlast("abc")); + }); +}; + +const test_last = async t => { + t.start("last()"); + + await t.testing("undefined for an empty array", () => { + t.assertEq(undefined, last([])); + t.assertEq(undefined, last("")); + }); + + await t.testing("the last element otherwise", () => { + t.assertEq(3, last([1, 2, 3])); + t.assertEq("c", last("abc")); + }); + + await t.testing("combines with butlast() well", () => { + const arr = ["anything", "goes", "here", "too"]; + t.assertEq(arr, [ ...butlast(arr), last(arr) ]); + }); +}; + +const test_take = async t => { + t.start("take()"); + + await t.testing("example usage", () => { + t.assertEq(Array.from(take(3, [1, 2, 3, 4, 5, 6])), [1, 2, 3]); + t.assertEq([...take(3, [1, 2, 3, 4, 5, 6])], [1, 2, 3]); + + const gen = function*() { + yield* [1, 2, 3, 4, 5, 6]; + } + t.assertEq([...take(3, gen())], [1, 2, 3]); + + t.assertEq([...take(3, [1, 2])], [1, 2]); + + t.assertEq([...take(1, [])], []); + t.assertEq([...take(0, [1])], []); + t.assertEq([...take(-1, [1])], []); + }); +}; + +const test_range = async t => { + t.start("range()"); + + await t.testing("empty values", () => { + const [] = range(); + + const [ a ] = range(); + t.assertEq(a, 0n); + + const [ b, c, d, e ] = range(); + t.assertEq( + [ b, c, d, e ], + [ 0n, 1n, 2n, 3n ], + ); + + t.assertEq( + Array.from(take(5, range())), + [ 0n, 1n, 2n, 3n, 4n ], + ); + + t.assertEq( + Array.from(take(1, range())), + [ 0n ], + ); + + t.assertEq( + Array.from(take(0, range())), + [], + ); + }); + + await t.testing("example usage", () => { + t.assertEq( + [...range(-5, 5)], + [ -5n, -4n, -3n, -2n, -1n, 0n, 1n, 2n, 3n, 4n ], + ); + t.assertEq( + [...range(-100, 100, 10)], + [ + -100n, -90n, -80n, -70n, -60n, -50n, -40n, -30n, + -20n, -10n, 0n, 10n, 20n, 30n, 40n, 50n, 60n, + 70n, 80n, 90n, + ], + ); + + t.assertEq([...range(0, 4, 2)], [0n, 2n]); + t.assertEq([...range(0, 5, 2)], [0n, 2n, 4n]); + t.assertEq([...range(0, 6, 2)], [0n, 2n, 4n]); + t.assertEq([...range(0, 7, 2)], [0n, 2n, 4n, 6n]); + + t.assertEq( + [...range(100, 0, -10)], + [ 100n, 90n, 80n, 70n, 60n, 50n, 40n, 30n, 20n, 10n ] + ); + t.assertEq( + [...range(10, -10, -1)], + [ + 10n, 9n, 8n, 7n, 6n, 5n, 4n, 3n, 2n, 1n, 0n, + -1n, -2n, -3n, -4n, -5n, -6n, -7n, -8n, -9n, + ], + ); + + t.assertEq( + [...take(3, range(1, 10, 0))], + [ 1n, 1n, 1n ], + ); + t.assertEq( + [...take(3, range(10, 1, 0))], + [ 10n, 10n, 10n ], + ); + }); +}; + + + +await runTests([ + test_getIn, + test_merge, + test_compareObject, + test_escape, + test_eq, + test_keys, + test_difference, + test_assocIn, + test_dissoc, + test_findFirst, + test_partial, + test_strSortFn, + test_undefinedAsNull, + test_first, + test_rest, + test_butlast, + test_last, + test_take, + test_range, +]); -- cgit v1.2.3