test(balance): cross-cutting integration tests (#144) #152

Merged
maximus merged 4 commits from issue-144-bilan-6 into main 2026-04-26 13:25:38 +00:00
Showing only changes of commit 5a54d37de5 - Show all commits

View file

@ -0,0 +1,96 @@
/**
* Non-regression check for the inlined transfer icon in TransactionTable
* (Issue #142 #144 follow-up).
*
* The spec promises that without any linked transfers the transactions
* table renders exactly as it did before #142 inlined the `<Link2>` icon.
* The icon is gated by a single conditional in the JSX:
*
* {linkedTransfersByTxId?.has(row.id) && (...)}
*
* If `linkedTransfersByTxId` is undefined OR the map has no entry for `row.id`,
* the icon block is short-circuited and the row layout is unchanged.
*
* Why this approach: this project does not bundle `@testing-library/react`
* (see `package.json`), and adding it just for one non-regression check is
* out of scope here. Existing component tests (`CategoryCombobox.test.ts`,
* `ViewModeToggle.test.ts`, `TrendsChartTypeToggle.test.ts`) likewise extract
* pure helpers and assert on them rather than mounting JSX. So we go one
* level lower: assert the source-level shape of `TransactionTable.tsx`.
*
* The assertions are structural on the source file:
* 1. The conditional block exists and is gated by `linkedTransfersByTxId?.has`.
* 2. The block consumes `Link2` from `lucide-react`.
* 3. The prop is OPTIONAL on the component's interface passing nothing
* must remain a valid call (zero-impact path).
* 4. The tooltip text comes from the i18n key family `transactions.transferIcon.*`
* (so a future rename catches our attention here).
* 5. The icon uses `aria-label` for accessibility (Issue #142 acceptance criterion).
* 6. The condition uses optional-chaining (so passing `undefined` short-circuits
* cleanly without throwing).
*
* If the icon is ever pulled out into its own component, the tests should be
* rewritten to import and exercise that component directly instead. Until
* then, this is a tight static contract that catches accidental regressions.
*/
import { describe, it, expect } from "vitest";
import { readFileSync } from "fs";
import { resolve } from "path";
const TABLE_SRC = readFileSync(
resolve(
import.meta.dirname,
"..",
"components",
"transactions",
"TransactionTable.tsx"
),
"utf-8"
);
describe("non-regression: TransactionTable transfer icon (#142)", () => {
it("guards the icon block behind `linkedTransfersByTxId?.has(row.id)`", () => {
expect(TABLE_SRC).toMatch(/linkedTransfersByTxId\?\.has\(row\.id\)/);
});
it("uses optional chaining so the icon is opt-in (undefined short-circuits)", () => {
// Optional chaining is the safe-render guarantee: if the parent never
// passes the prop, `?.has` returns undefined → the && short-circuits to
// false, the JSX block is skipped, and the row layout is unchanged.
expect(TABLE_SRC).toMatch(/linkedTransfersByTxId\?\./);
});
it("imports `Link2` from lucide-react for the icon glyph", () => {
expect(TABLE_SRC).toMatch(/from\s+["']lucide-react["']/);
expect(TABLE_SRC).toMatch(/\bLink2\b/);
});
it("declares `linkedTransfersByTxId` as an OPTIONAL prop", () => {
// The "?" after the name on the interface is the contract that omitting
// the prop is allowed. Without it the entire transactions page would
// need to thread the lookup through, breaking pre-#142 callers.
expect(TABLE_SRC).toMatch(/linkedTransfersByTxId\?:/);
});
it("uses `transactions.transferIcon.*` i18n keys for the tooltip and aria-label", () => {
// Both the tooltip body and the aria label go through i18n — neither
// is a hardcoded English/French string.
expect(TABLE_SRC).toMatch(/transactions\.transferIcon\.tooltip/);
expect(TABLE_SRC).toMatch(/transactions\.transferIcon\.ariaLabel/);
});
it("attaches an `aria-label` for screen readers (a11y)", () => {
expect(TABLE_SRC).toMatch(/aria-label=/);
});
it("keeps the description column structure shared with non-linked rows", () => {
// The icon lives inside the description cell, in a flex container
// alongside the original `<span class="truncate" title=...>` that
// existed pre-#142. If someone moved the description span into a
// wrapper that the icon required, this assertion would fail.
expect(TABLE_SRC).toMatch(
/<span\s+className="truncate"\s+title=\{row\.description\}/
);
});
});