Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,42 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
## [Unreleased]

### Fixed
- The redesigned Comparison page's percentile gauge crashed the whole page at
render time (a styled-components `keyframes` object was interpolated into
a plain inline `style` string instead of an actual styled-components
template, which styled-components deliberately throws on) - visible in
the browser console as `Uncaught Error ... errors.md#12`, reachable the
moment benchmark consent was on and the gauge had a value to animate.
Added a render smoke test for `Comparison` so this class of runtime-only
bug (invisible to lint/types/pure-function tests) fails a test run again.
- Comparison page polish from live-testing on a deployed preview:
- The percentile gauge is now a full ring instead of a semicircle dome, and
its number/label are centered with plain flexbox instead of a hand-tuned
`top: 54%` guess - the old shape made pixel-perfect centering inherently
fiddly; a full circle's visual center trivially matches its bounding box.
- The gauge caption and the three headline chips (net worth/income/
frugality percentiles) didn't explain what the percentage actually meant.
The caption now states it explicitly ("higher than X% of people with a
similar profile"), and a new line under the chips clarifies that higher
is always better, including for the frugality (outflow) one.
- The info icon next to "Your comparison group" rendered black in dark
mode (no theme-aware color was set, so it fell back to the browser
default instead of inheriting one) - same class of bug as any other
icon with no explicit color, now fixed here explicitly since no
centralized icon-color helper exists in this codebase.
- "Spending by Category" only ever showed the user's own amounts - added
the same "vs. your comparison group" line every other accordion section
already has, per category.
- The gauge caption (the sentence explaining what the percentile means) was
missing `margin: 0 auto` on its `max-width`-constrained box, so it hugged
the left edge of the card instead of centering under the now-circular
gauge above it.
- Insert Data's "outflows"/"income" secondary tools row (CSV/Excel,
Ricorrenti, Spese condivise) stayed capped at an old 1000px width on
desktop while the page around it (`ContentWrapper`/`SectionCard`) was
widened to 1400px in a previous change, leaving a wide dead strip to its
right instead of right-aligning against the same edge as the form card
beneath it.
- Consistency and correctness pass across the CSV import wizard and the
Aggiungi entrate/uscite flow, prompted by live-testing a real Trade
Republic export:
Expand Down
88 changes: 88 additions & 0 deletions src/__tests__/sections/Comparison.render.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* Smoke-render test for the redesigned Comparison page. The hero gauge's
* SVG arc previously interpolated a styled-components `keyframes` object
* into a plain inline `style` object instead of a real styled-components
* tagged template - that only throws at render time (styled-components
* error #12), so no lint/type/unit check on pure functions ever caught it.
* This test actually mounts <Comparison> with demo-realistic data so that
* class of bug fails a test run instead of only showing up in production.
*/
import { describe, it, expect, vi } from 'vitest';
import { render } from '@testing-library/react';
import React from 'react';
import { ThemeContext } from '../../contexts/ThemeContext';
import { LanguageContext } from '../../contexts/LanguageContext';
import { CurrencyContext } from '../../contexts/CurrencyContext';
import enLocale from '../../i18n/locales/en.json';
import { generateDemoData } from '../../data/demoData';

vi.mock('../../hooks/useLocalizedNavigate', () => ({
useLocalizedNavigate: () => vi.fn(),
}));

vi.mock('../../hooks/useDemoServices', () => ({
useDemoServices: () => ({
rankingService: {
previewCustomBenchmark: vi.fn().mockResolvedValue({
requestedFactors: [], factors: [], relaxed: false, available: true,
cohort: { size: 34, populationSize: 214, minimumSize: 20, averageSimilarity: 0.74 },
}),
getCustomBenchmark: vi.fn().mockResolvedValue({
available: true, requestedFactors: [], factors: [], relaxed: false, generatedAt: new Date().toISOString(),
cohort: { size: 34, populationSize: 214, minimumSize: 20, averageSimilarity: 0.74 },
averages: { balances: 36859, incomes: 2506, expenses: 1358, assetAllocation: { liquid: 38, investments: 50, crypto: 12 } },
rankings: { balance: 78, incomes: 68, outflows: 38 },
}),
},
userService: {
setBenchmarkConsent: vi.fn().mockResolvedValue({ benchmarkConsent: true }),
},
statsService: {
getBehaviourBenchmark: vi.fn().mockResolvedValue({
available: true, minimumCohortSize: 20, cohortSize: 214,
personal: { savingConsistency: 78, investmentRegularity: 65, contributionFrequency: 3.2, goalProgress: 54 },
rankings: { savingConsistency: 72, investmentRegularity: 68, contributionFrequency: 55, goalProgress: 61 },
}),
},
}),
}));

vi.mock('../../contexts/DeploymentContext', () => ({
useDeployment: () => ({ selfHosted: false }),
}));

import Comparison from '../../sections/Comparison';

const theme = { mode: 'light', textColor: '#000', buttonBackgroundColor: '#079164' };
const currencyCtx = {
formatAmount: (v) => `€${Number(v).toFixed(0)}`,
};
const translations = {
comparison: enLocale.comparison,
general: { comingSoon: 'Coming soon' },
};

function renderComparison(userData) {
return render(
<ThemeContext.Provider value={{ theme }}>
<LanguageContext.Provider value={{ language: 'en', translations }}>
<CurrencyContext.Provider value={currencyCtx}>
<Comparison theme={theme} userData={userData} isHidden={false} />
</CurrencyContext.Provider>
</LanguageContext.Provider>
</ThemeContext.Provider>,
);
}

describe('Comparison (render smoke test)', () => {
it('renders the hero gauge and accordion without throwing, with demo data (benchmarkConsent true)', () => {
const userData = generateDemoData();
expect(userData.benchmarkConsent).toBe(true);
expect(() => renderComparison(userData)).not.toThrow();
});

it('renders the opt-in gate without throwing when benchmark consent has not been given', () => {
const userData = { ...generateDemoData(), benchmarkConsent: false };
expect(() => renderComparison(userData)).not.toThrow();
});
});
3 changes: 2 additions & 1 deletion src/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1192,7 +1192,8 @@
"hero": {
"eyebrow": "Nur aggregierte, anonyme Daten",
"gaugeLabel": "Perzentil",
"gaugeCaption": "Dein Vermögen im Vergleich zu Personen mit ähnlichem Profil.",
"gaugeCaption": "Höher als bei {value}% der Personen mit einem ähnlichen Profil wie deinem.",
"chipsCaption": "Perzentile im Vergleich zu deiner Vergleichsgruppe — höher ist immer besser, auch bei den Ausgaben.",
"gaugeLockedDescription": "Wir zeigen dies, sobald deine Vergleichsgruppe die Mindestgröße zum Datenschutz erreicht.",
"balanceLabel": "Vermögen",
"incomeLabel": "Einkommen",
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1260,7 +1260,8 @@
"hero": {
"eyebrow": "Aggregate, anonymous data only",
"gaugeLabel": "Percentile",
"gaugeCaption": "Your net worth compared to people with a similar profile.",
"gaugeCaption": "Higher than {value}% of people with a similar profile to yours.",
"chipsCaption": "Percentiles vs. your comparison group - higher is always better, including for frugality.",
"gaugeLockedDescription": "We'll show this as soon as your comparison group reaches the minimum privacy threshold.",
"balanceLabel": "Net worth",
"incomeLabel": "Income",
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1194,7 +1194,8 @@
"hero": {
"eyebrow": "Solo datos agregados y anónimos",
"gaugeLabel": "Percentil",
"gaugeCaption": "Tu patrimonio comparado con personas de perfil similar.",
"gaugeCaption": "Más alto que el {value}% de las personas con un perfil similar al tuyo.",
"chipsCaption": "Percentiles frente a tu grupo de comparación — cuanto más alto, mejor, también en el gasto.",
"gaugeLockedDescription": "Lo mostraremos en cuanto tu grupo de comparación alcance el umbral mínimo de privacidad.",
"balanceLabel": "Patrimonio",
"incomeLabel": "Ingresos",
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1192,7 +1192,8 @@
"hero": {
"eyebrow": "Uniquement des données agrégées et anonymes",
"gaugeLabel": "Percentile",
"gaugeCaption": "Ton patrimoine comparé à des personnes au profil similaire.",
"gaugeCaption": "Supérieur à {value}% des personnes ayant un profil similaire au tien.",
"chipsCaption": "Percentiles par rapport à ton groupe de comparaison — plus c'est élevé, mieux c'est, y compris pour les dépenses.",
"gaugeLockedDescription": "Nous l'afficherons dès que ton groupe de comparaison atteindra le seuil minimal de confidentialité.",
"balanceLabel": "Patrimoine",
"incomeLabel": "Revenus",
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -1260,7 +1260,8 @@
"hero": {
"eyebrow": "Solo dati aggregati e anonimi",
"gaugeLabel": "Percentile",
"gaugeCaption": "Il tuo patrimonio rispetto a persone con un profilo simile al tuo.",
"gaugeCaption": "Il tuo patrimonio supera quello del {value}% delle persone con un profilo simile al tuo.",
"chipsCaption": "Percentili rispetto al tuo gruppo di confronto — più alto è sempre meglio, anche per la spesa.",
"gaugeLockedDescription": "Lo mostreremo appena il tuo gruppo di confronto raggiungerà la soglia minima di privacy.",
"balanceLabel": "Patrimonio",
"incomeLabel": "Entrate",
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/locales/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -1192,7 +1192,8 @@
"hero": {
"eyebrow": "Apenas dados agregados e anônimos",
"gaugeLabel": "Percentil",
"gaugeCaption": "Seu patrimônio comparado a pessoas com perfil semelhante.",
"gaugeCaption": "Maior que o de {value}% das pessoas com um perfil semelhante ao seu.",
"chipsCaption": "Percentis em relação ao seu grupo de comparação — quanto maior, melhor, inclusive nos gastos.",
"gaugeLockedDescription": "Vamos mostrar isso assim que seu grupo de comparação atingir o limite mínimo de privacidade.",
"balanceLabel": "Patrimônio",
"incomeLabel": "Renda",
Expand Down
Loading
Loading