// The view's pure decisions: the decimal⇄rational bridge, the book, and the
// projections. Prices never touch a double, and these tests are where that is
// cheap to prove — a conversion that rounded would fail the round trips.
import test from "node:test";
import assert from "node:assert/strict";
import * as L from "../test-dist/testlib.js";
import {
ACTORS, PAIR, SEED_MAKER, SEED_TAKER, TS_FIXED,
entry, makeQuote, opQuote, opTake,
} from "./vectors/independent.mjs";
const { makerPub: MAKER, takerPub: TAKER } = ACTORS;
const T = TS_FIXED;
async function snap(entries) {
const f = new L.BursaFold();
for (const e of structuredClone(entries)) f.ingest(e);
await f.awaitVerified();
return f.snapshot();
}
// ---- decimal ⇄ rational -----------------------------------------------------
test("a human's decimal price becomes the wire's rational, dec exponents included", () => {
// equal exponents: 1.50 quote-major per base-major = 3/2 minor per minor
assert.deepEqual(L.pxOfDecimal("1.50", 2, 2), { num: 3, den: 2 });
assert.deepEqual(L.pxOfDecimal("1", 2, 2), { num: 1, den: 1 });
// differing exponents scale by 10^(qdec-bdec)
assert.deepEqual(L.pxOfDecimal("1", 0, 2), { num: 100, den: 1 });
assert.deepEqual(L.pxOfDecimal("1", 2, 0), { num: 1, den: 100 });
assert.deepEqual(L.pxOfDecimal("0.07", 2, 2), { num: 7, den: 100 });
});
test("unpriceable input is nil, not a guess", () => {
for (const bad of ["0", "0.00", "", "-1", "1e5", "1,5", ".5", "abc", null, 7]) {
assert.equal(L.pxOfDecimal(bad, 2, 2), null, JSON.stringify(bad));
}
});
test("the decimal round-trips, and display TRUNCATES rather than promising more", () => {
const px = L.pxOfDecimal("1.50", 2, 2);
assert.equal(L.pxToDecimal(px, 2, 2, 4), "1.5");
assert.equal(L.pxToDecimal({ num: 1, den: 3 }, 2, 2, 4), "0.3333", "…never 0.3334");
assert.equal(L.pxToDecimal({ num: 100, den: 1 }, 0, 2, 2), "1", "scaling reverses");
assert.equal(L.pxToDecimal({ num: 7, den: 100 }, 2, 2, 2), "0.07");
});
// ---- the book -----------------------------------------------------------------
test("bids sort best-first descending, asks ascending, ties in fold order", async () => {
const q = (idN, side, num) => makeQuote({ seed: SEED_MAKER, idN, side, px: { num, den: 1 }, qty: 100 });
const s = await snap([
entry("h01", 1, MAKER, opQuote(T, q(1, "ask", 5))),
entry("h02", 2, MAKER, opQuote(T, q(2, "ask", 3))),
entry("h03", 3, MAKER, opQuote(T, q(3, "bid", 1))),
entry("h04", 4, MAKER, opQuote(T, q(4, "bid", 2))),
entry("h05", 5, MAKER, opQuote(T, q(5, "bid", 2))), // tie: fold order holds
]);
const b = L.bookOf(s, PAIR);
assert.deepEqual(b.asks.map((x) => x.o.px.num), [3, 5], "lowest ask first");
assert.deepEqual(b.bids.map((x) => x.h), ["h04", "h05", "h03"], "highest bid first, tie stable");
assert.deepEqual([...L.bookPairs(s)], [PAIR]);
});
test("the projections know whose is whose", async () => {
const ask = makeQuote({ seed: SEED_MAKER, idN: 1, side: "ask", px: { num: 3, den: 2 }, qty: 1000 });
const s = await snap([
entry("h01", 1, MAKER, opQuote(T, ask)),
entry("h02", 2, TAKER, opTake(T, "h01", 300)),
]);
assert.equal(L.myQuotes(s, MAKER).length, 1);
assert.equal(L.myQuotes(s, TAKER).length, 0);
assert.equal(L.myMatches(s, MAKER).length, 1, "the maker is a party");
assert.equal(L.myMatches(s, TAKER).length, 1, "and so is the taker");
assert.equal(L.myMatches(s, ACTORS.bankBPub).length, 0);
});