|
1 | 1 | import { describe, expect, it } from "vitest"; |
2 | 2 | import { |
3 | 3 | asBigInt, |
| 4 | + columnKind, |
4 | 5 | columnLabel, |
5 | 6 | compareValues, |
6 | 7 | displayAmount, |
| 8 | + explorerHref, |
| 9 | + headerHelp, |
| 10 | + headerLabel, |
| 11 | + kpiStandsAlone, |
| 12 | + rowMatches, |
| 13 | + toCsv, |
7 | 14 | isNumericColumn, |
8 | 15 | formatCell, |
9 | 16 | groupDigits, |
@@ -359,3 +366,106 @@ describe("isNumericColumn", () => { |
359 | 366 | expect(isNumericColumn(column, [null, null])).toBe(false); |
360 | 367 | }); |
361 | 368 | }); |
| 369 | + |
| 370 | +const ADDR = "0x9cf687eaca65e5da625d3fde534a73513b0a959f"; |
| 371 | +const TX = "0xdda2c8a4868abbd3613be2bd5621746ba1441e69f93618bf3573095fd9ec1166"; |
| 372 | +const text = (name: string) => ({ name, logical_type: "VARCHAR" }); |
| 373 | + |
| 374 | +describe("columnKind", () => { |
| 375 | + it("reads a column of 20-byte hex values as addresses", () => { |
| 376 | + expect(columnKind(text("wallet"), undefined, [ADDR, null, ADDR])).toBe("address"); |
| 377 | + }); |
| 378 | + |
| 379 | + it("never guesses a 32-byte value is a transaction", () => { |
| 380 | + // It may as well be a bytes32 order id; only a name or a declaration says tx. |
| 381 | + expect(columnKind(text("id"), undefined, [TX])).toBe("text"); |
| 382 | + expect(columnKind(text("tx_hash"), undefined, [TX])).toBe("tx"); |
| 383 | + expect(columnKind(text("hash"), { kind: "tx" }, [TX])).toBe("tx"); |
| 384 | + }); |
| 385 | + |
| 386 | + it("knows block_number by name, and lets a declaration turn detection off", () => { |
| 387 | + expect(columnKind({ name: "block_number", logical_type: "BIGINT" }, undefined, ["1"])).toBe("block"); |
| 388 | + expect(columnKind(text("wallet"), { kind: "text" }, [ADDR])).toBe("text"); |
| 389 | + }); |
| 390 | + |
| 391 | + it("reads a mixed or empty column as text", () => { |
| 392 | + expect(columnKind(text("who"), undefined, [ADDR, "FOMO"])).toBe("text"); |
| 393 | + expect(columnKind(text("who"), undefined, [null])).toBe("text"); |
| 394 | + }); |
| 395 | +}); |
| 396 | + |
| 397 | +describe("explorerHref", () => { |
| 398 | + const base = "https://explorer.example"; |
| 399 | + |
| 400 | + it("builds the Etherscan-style path for each kind", () => { |
| 401 | + expect(explorerHref(base, "address", ADDR)).toBe(`${base}/address/${ADDR}`); |
| 402 | + expect(explorerHref(base, "tx", TX)).toBe(`${base}/tx/${TX}`); |
| 403 | + expect(explorerHref(base, "block", "76491309")).toBe(`${base}/block/76491309`); |
| 404 | + }); |
| 405 | + |
| 406 | + it("refuses a value that is not the kind it claims, and works without a base", () => { |
| 407 | + expect(explorerHref(base, "tx", ADDR)).toBeNull(); |
| 408 | + expect(explorerHref(base, "block", "0x10")).toBeNull(); |
| 409 | + expect(explorerHref(base, "text", ADDR)).toBeNull(); |
| 410 | + expect(explorerHref(null, "address", ADDR)).toBeNull(); |
| 411 | + }); |
| 412 | +}); |
| 413 | + |
| 414 | +describe("header label and help", () => { |
| 415 | + const amount = { name: "usdg", logical_type: "VARCHAR", raw_amount: true, decimals: 6, label: "USDG" }; |
| 416 | + |
| 417 | + it("prefers the panel's label, then the amount label, then the name", () => { |
| 418 | + expect(headerLabel(amount, { label: "USDG sold" })).toBe("USDG sold"); |
| 419 | + expect(headerLabel(amount)).toBe("USDG"); |
| 420 | + expect(headerLabel(text("tx_hash"))).toBe("Tx hash"); |
| 421 | + }); |
| 422 | + |
| 423 | + it("explains a raw amount unless the panel says something itself", () => { |
| 424 | + expect(headerHelp(amount)).toContain("6 decimals"); |
| 425 | + expect(headerHelp(amount, { description: "What the sells fetched." })).toBe("What the sells fetched."); |
| 426 | + expect(headerHelp(text("wallet"))).toBeNull(); |
| 427 | + }); |
| 428 | +}); |
| 429 | + |
| 430 | +describe("rowMatches", () => { |
| 431 | + const row = ["VRAX 9464", ADDR, "4806"]; |
| 432 | + |
| 433 | + it("matches any visible cell, ignoring case and surrounding space", () => { |
| 434 | + expect(rowMatches(row, [0, 1, 2], " vrax ")).toBe(true); |
| 435 | + expect(rowMatches(row, [0, 1, 2], "0x9CF6")).toBe(true); |
| 436 | + expect(rowMatches(row, [0, 2], "0x9cf6")).toBe(false); |
| 437 | + expect(rowMatches(row, [0], "")).toBe(true); |
| 438 | + }); |
| 439 | +}); |
| 440 | + |
| 441 | +describe("toCsv", () => { |
| 442 | + it("writes exact values under the query's own column names", () => { |
| 443 | + const columns = [text("token"), { name: "usdg", logical_type: "VARCHAR", raw_amount: true, decimals: 6 }]; |
| 444 | + const csv = toCsv(columns, [["VRAX, 9464", "813817279396"], ['say "hi"', null]], [0, 1]); |
| 445 | + expect(csv).toBe('token,usdg\r\n"VRAX, 9464",813817279396\r\n"say ""hi""",\r\n'); |
| 446 | + }); |
| 447 | + |
| 448 | + it("leaves hidden columns out", () => { |
| 449 | + expect(toCsv([text("a"), text("b")], [["1", "2"]], [1])).toBe("b\r\n2\r\n"); |
| 450 | + }); |
| 451 | +}); |
| 452 | + |
| 453 | +describe("kpiStandsAlone", () => { |
| 454 | + it("frees a KPI from a non-KPI row-mate, and only then", () => { |
| 455 | + expect( |
| 456 | + kpiStandsAlone([ |
| 457 | + { chart: "kpi" }, { chart: "kpi" }, // level pair |
| 458 | + { chart: "kpi" }, { chart: "table" }, // KPI beside a table |
| 459 | + { chart: "bar", span: "full" }, |
| 460 | + { chart: "line" }, { chart: "kpi" }, // table-first order too |
| 461 | + { chart: "kpi" }, // last, with no row-mate |
| 462 | + ]), |
| 463 | + ).toEqual([false, false, true, false, false, false, true, false]); |
| 464 | + }); |
| 465 | + |
| 466 | + it("starts a fresh row after a full-width panel", () => { |
| 467 | + expect( |
| 468 | + kpiStandsAlone([{ chart: "kpi" }, { chart: "table", span: "full" }, { chart: "table" }, { chart: "kpi" }]), |
| 469 | + ).toEqual([false, false, false, true]); |
| 470 | + }); |
| 471 | +}); |
0 commit comments