* docs: add portfolio chart design doc for analytics page Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: add portfolio chart implementation plan Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add buildDailyBalances helper for portfolio chart Implements pure function to reconstruct daily closing balances for the current calendar month from transaction data. Used by analytics dashboard to display balance history per day. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: guard buildDailyBalances against cross-month transactions * feat: add PortfolioChart SVG component * fix: use unique gradient id in PortfolioChart to prevent collision * feat: add portfolio value chart to analytics page Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: budget card was showing spentTotal instead of leftToSpend * docs: add balance pie chart design doc * docs: add analytics charts redesign doc * adding chart library * making sure privacy sets for each wallet * fixing pos server name bug * switch toggle theme to analytics page * fixing text align for close pool * adding monthly buget to masterInfoObject * creating AnalyticsProvider * adding buget constants * use new chart library * building analytics homepage * creating sent and received analytics pages * creating budget pages * adding analytics to navigation pages * add budget helpers * creating buget warning component * adding budget warning to send pages * fixed spelling bug * fixing color inconsistancy * fix dup key bug * making sure to warn if current payment puts user over or near budget limit * adding translations * fix preset amount format --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
61 lines
1.9 KiB
JavaScript
61 lines
1.9 KiB
JavaScript
import { computePieSegments } from '../app/functions/CustomElements/balancePieChartHelpers';
|
|
|
|
const BASE = {
|
|
bitcoinBalance: 100_000,
|
|
dollarBalanceSat: 50_000,
|
|
dollarBalanceToken: 30,
|
|
savingsBalance: 10,
|
|
btcPrice: 100_000_000,
|
|
};
|
|
|
|
// dollarsToSats(dollars, price) = (dollars * 1_000_000) / price
|
|
// with btcPrice=100_000_000: dollarsToSats(10) = 10_000_000 / 100_000_000 = 0.1 sats
|
|
|
|
describe('computePieSegments', () => {
|
|
it('sats mode: bitcoin stays in sats, dollar combines dollarBalanceSat + savingsSat', () => {
|
|
const result = computePieSegments({ ...BASE, denomination: 'sats' });
|
|
expect(result.btcValue).toBe(100_000);
|
|
const savingsSat = (10 * 1_000_000) / 100_000_000;
|
|
expect(result.dollarValue).toBeCloseTo(50_000 + savingsSat, 5);
|
|
});
|
|
|
|
it('hidden denomination treated same as sats', () => {
|
|
const result = computePieSegments({ ...BASE, denomination: 'hidden' });
|
|
expect(result.btcValue).toBe(100_000);
|
|
});
|
|
|
|
it('fiat mode: bitcoin converted to USD, dollar combines dollarBalanceToken + savingsBalance', () => {
|
|
const result = computePieSegments({
|
|
...BASE,
|
|
btcPrice: 100_000,
|
|
denomination: 'fiat',
|
|
});
|
|
// 100_000 sats = 0.001 BTC * $100,000 = $100
|
|
expect(result.btcValue).toBeCloseTo(100, 5);
|
|
expect(result.dollarValue).toBeCloseTo(40, 5); // $30 + $10
|
|
});
|
|
|
|
it('returns zeros when all balances are zero', () => {
|
|
const result = computePieSegments({
|
|
bitcoinBalance: 0,
|
|
dollarBalanceSat: 0,
|
|
dollarBalanceToken: 0,
|
|
savingsBalance: 0,
|
|
btcPrice: 100_000,
|
|
denomination: 'sats',
|
|
});
|
|
expect(result.btcValue).toBe(0);
|
|
expect(result.dollarValue).toBe(0);
|
|
});
|
|
|
|
it('handles zero btcPrice gracefully in fiat mode', () => {
|
|
const result = computePieSegments({
|
|
...BASE,
|
|
btcPrice: 0,
|
|
denomination: 'fiat',
|
|
});
|
|
expect(result.btcValue).toBe(0);
|
|
expect(result.dollarValue).toBeCloseTo(40, 5);
|
|
});
|
|
});
|