Align spending report groups and totals with gross outflows
This commit is contained in:
@@ -88,12 +88,15 @@ export const YnabSpendingReport = ({ data }: RendererProps<YnabSpendingReportDat
|
||||
<Subtle>Grouped by {data.group_by?.replace(/_/g, " ")}</Subtle>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-wide text-warm-gray">Total</div>
|
||||
<div className="text-[11px] font-semibold uppercase tracking-wide text-warm-gray">{data.total_basis === "outflows" ? "Total outflows" : "Total"}</div>
|
||||
<div className="text-xl font-bold tabular-nums text-charcoal">{formatMoney(data.total)}</div>
|
||||
{data.inflows != null && <Subtle>Inflows: {formatMoney(data.inflows)}</Subtle>}
|
||||
{data.net_outflows != null && <Subtle>Net outflows: {formatMoney(data.net_outflows)}</Subtle>}
|
||||
</div>
|
||||
</div>
|
||||
{data.total_basis === "outflows" && <Subtle>Groups show gross outflows. Refunds and other inflows are reported separately.</Subtle>}
|
||||
{rows.length === 0 ? (
|
||||
<EmptyNote>No spending in this period.</EmptyNote>
|
||||
<EmptyNote>No outflows in this period.</EmptyNote>
|
||||
) : data.group_by === "month" ? (
|
||||
<MonthBars rows={rows} />
|
||||
) : (
|
||||
|
||||
@@ -87,9 +87,13 @@ export type YnabSpendingReportData = {
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
group_by: "category" | "category_group" | "payee" | "account" | "month" | string;
|
||||
/** amount is positive for spending (-SUM); a net refund comes out negative. */
|
||||
/** Gross outflows for new reports; legacy reports contain net spending. */
|
||||
rows: { key: string; amount: number; count?: number }[];
|
||||
total: number;
|
||||
total_basis?: "outflows";
|
||||
inflows?: number;
|
||||
net_outflows?: number;
|
||||
filters?: { category: string; payee: string };
|
||||
};
|
||||
|
||||
export type YnabCategoryBudgetData = {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
const fs = require('node:fs');
|
||||
const assert = require('node:assert/strict');
|
||||
const test = require('node:test');
|
||||
const ts = require('typescript');
|
||||
const React = require('react');
|
||||
const { renderToStaticMarkup } = require('react-dom/server');
|
||||
|
||||
// Load the existing TSX renderer without adding a test bundler dependency.
|
||||
for (const ext of ['.ts', '.tsx']) {
|
||||
require.extensions[ext] = (mod, filename) => {
|
||||
const { outputText } = ts.transpileModule(fs.readFileSync(filename, 'utf8'), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, jsx: ts.JsxEmit.ReactJSX, esModuleInterop: true },
|
||||
});
|
||||
mod._compile(outputText, filename);
|
||||
};
|
||||
}
|
||||
const { YnabSpendingReport } = require('../src/components/artifacts/renderers/YnabSpendingReport.tsx');
|
||||
const base = { start_date: '2026-10-01', end_date: '2026-10-12', group_by: 'category', rows: [{ key: 'Shopping', amount: 70, count: 2 }], total: 100 };
|
||||
const render = (data) => renderToStaticMarkup(React.createElement(YnabSpendingReport, { data }));
|
||||
|
||||
test('legacy artifact keeps a neutral total label', () => {
|
||||
const html = render(base);
|
||||
assert.match(html, />Total</);
|
||||
assert.doesNotMatch(html, /Total \(net\)|Total outflows|Inflows:/);
|
||||
});
|
||||
test('new report labels gross outflows, inflows and net separately', () => {
|
||||
const html = render({ ...base, total_basis: 'outflows', inflows: 30, net_outflows: 70 });
|
||||
assert.match(html, /Total outflows/);
|
||||
assert.match(html, /Inflows:/);
|
||||
assert.match(html, /Net outflows:/);
|
||||
assert.match(html, /Groups show gross outflows/);
|
||||
});
|
||||
test('inflow-only report remains visible with empty outflow rows', () => {
|
||||
const html = render({ ...base, total_basis: 'outflows', rows: [], total: 0, inflows: 75, net_outflows: -75 });
|
||||
assert.match(html, /No outflows in this period/);
|
||||
assert.match(html, /Inflows:/);
|
||||
});
|
||||
Reference in New Issue
Block a user