This commit is contained in:
2025-12-06 15:19:53 +01:00
parent 46dd28482f
commit 8ac1c1a9df

View File

@@ -1,13 +1,19 @@
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import FireCalculatorForm from "../FireCalculatorForm";
import { describe, it, expect, vi, beforeAll } from "vitest";
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import FireCalculatorForm from '../FireCalculatorForm';
import { describe, it, expect, vi, beforeAll } from 'vitest';
// Mocking ResizeObserver
class ResizeObserver {
observe() { /* noop */ }
unobserve() { /* noop */ }
disconnect() { /* noop */ }
observe() {
/* noop */
}
unobserve() {
/* noop */
}
disconnect() {
/* noop */
}
}
global.ResizeObserver = ResizeObserver;
@@ -20,115 +26,117 @@ beforeAll(() => {
});
// Mock Recharts ResponsiveContainer
vi.mock("recharts", async () => {
const originalModule = await vi.importActual("recharts");
vi.mock('recharts', async () => {
const originalModule = await vi.importActual('recharts');
return {
...originalModule,
ResponsiveContainer: ({ children }: { children: React.ReactNode }) => (
<div style={{ width: "500px", height: "300px" }}>{children}</div>
<div style={{ width: '500px', height: '300px' }}>{children}</div>
),
};
});
describe("FireCalculatorForm", () => {
it("renders the form with default values", () => {
describe('FireCalculatorForm', () => {
it('renders the form with default values', () => {
render(<FireCalculatorForm />);
expect(screen.getByText("FIRE Calculator")).toBeInTheDocument();
expect(screen.getByLabelText(/Starting Capital/i)).toHaveValue(50000);
expect(screen.getByLabelText(/Monthly Savings/i)).toHaveValue(1500);
expect(screen.getByLabelText(/Current Age/i)).toHaveValue(25);
expect(screen.getByText('FIRE Calculator')).toBeInTheDocument();
expect(screen.getByRole('spinbutton', { name: /Starting Capital/i })).toHaveValue(50000);
expect(screen.getByRole('spinbutton', { name: /Monthly Savings/i })).toHaveValue(1500);
expect(screen.getByRole('spinbutton', { name: /Current Age/i })).toHaveValue(25);
});
it("calculates and displays results when submitted", async () => {
it('calculates and displays results when submitted', async () => {
const user = userEvent.setup();
render(<FireCalculatorForm />);
const calculateButton = screen.getByRole("button", { name: /Calculate/i });
const calculateButton = screen.getByRole('button', { name: /Calculate/i });
await user.click(calculateButton);
await waitFor(() => {
expect(screen.getByText("Financial Projection")).toBeInTheDocument();
expect(screen.getByText("FIRE Number")).toBeInTheDocument();
expect(screen.getByText('Financial Projection')).toBeInTheDocument();
expect(screen.getByText('FIRE Number')).toBeInTheDocument();
});
});
it("allows changing inputs", () => {
// using fireEvent for reliability with number inputs in jsdom
render(<FireCalculatorForm />);
const savingsInput = screen.getByLabelText(/Monthly Savings/i);
fireEvent.change(savingsInput, { target: { value: "2000" } });
expect(savingsInput).toHaveValue(2000);
});
it("validates inputs", async () => {
const user = userEvent.setup();
render(<FireCalculatorForm />);
const ageInput = screen.getByLabelText(/Current Age/i);
// Use fireEvent to set invalid value directly
fireEvent.change(ageInput, { target: { value: "-5" } });
const calculateButton = screen.getByRole("button", { name: /Calculate/i });
await user.click(calculateButton);
// Look for error message text
expect(await screen.findByText(/Age must be at least 1/i)).toBeInTheDocument();
it('allows changing inputs', () => {
// using fireEvent for reliability with number inputs in jsdom
render(<FireCalculatorForm />);
const savingsInput = screen.getByRole('spinbutton', { name: /Monthly Savings/i });
fireEvent.change(savingsInput, { target: { value: '2000' } });
expect(savingsInput).toHaveValue(2000);
});
it("toggles Monte Carlo simulation mode", async () => {
it('validates inputs', async () => {
const user = userEvent.setup();
render(<FireCalculatorForm />);
const ageInput = screen.getByRole('spinbutton', { name: /Current Age/i });
// Use fireEvent to set invalid value directly
fireEvent.change(ageInput, { target: { value: '-5' } });
const calculateButton = screen.getByRole('button', { name: /Calculate/i });
await user.click(calculateButton);
// Look for error message text
expect(await screen.findByText(/Age must be at least 1/i)).toBeInTheDocument();
});
it('toggles Monte Carlo simulation mode', async () => {
const user = userEvent.setup();
render(<FireCalculatorForm />);
// Select Trigger
const modeTrigger = screen.getByRole("combobox", { name: /Simulation Mode/i });
const modeTrigger = screen.getByRole('combobox', { name: /Simulation Mode/i });
await user.click(modeTrigger);
// Select Monte Carlo from dropdown
const monteCarloOption = await screen.findByRole("option", { name: /Monte Carlo/i });
const monteCarloOption = await screen.findByRole('option', { name: /Monte Carlo/i });
await user.click(monteCarloOption);
// Verify Volatility input appears
expect(await screen.findByLabelText(/Market Volatility/i)).toBeInTheDocument();
expect(await screen.findByRole('spinbutton', { name: /Market Volatility/i })).toBeInTheDocument();
});
it("toggles 4% Rule overlay", async () => {
it('toggles 4% Rule overlay', async () => {
const user = userEvent.setup();
render(<FireCalculatorForm />);
// Calculate first to show results
const calculateButton = screen.getByRole("button", { name: /Calculate/i });
const calculateButton = screen.getByRole('button', { name: /Calculate/i });
await user.click(calculateButton);
// Wait for results
await waitFor(() => {
expect(screen.getByText("Financial Projection")).toBeInTheDocument();
expect(screen.getByText('Financial Projection')).toBeInTheDocument();
});
// Find the Show 4%-Rule button
const showButton = screen.getByRole("button", { name: /Show 4%-Rule/i });
const showButton = screen.getByRole('button', { name: /Show 4%-Rule/i });
await user.click(showButton);
// Should now see 4%-Rule stats
expect(await screen.findByText("4%-Rule FIRE Number")).toBeInTheDocument();
expect(await screen.findByText('4%-Rule FIRE Number')).toBeInTheDocument();
// Button text should change
expect(screen.getByRole("button", { name: /Hide 4%-Rule/i })).toBeInTheDocument();
expect(screen.getByRole('button', { name: /Hide 4%-Rule/i })).toBeInTheDocument();
});
it("handles withdrawal strategy selection", async () => {
it('handles withdrawal strategy selection', async () => {
const user = userEvent.setup();
render(<FireCalculatorForm />);
const strategyTrigger = screen.getByRole("combobox", { name: /Withdrawal Strategy/i });
const strategyTrigger = screen.getByRole('combobox', { name: /Withdrawal Strategy/i });
await user.click(strategyTrigger);
const percentageOption = await screen.findByRole("option", { name: /Percentage of Portfolio/i });
const percentageOption = await screen.findByRole('option', { name: /Percentage of Portfolio/i });
await user.click(percentageOption);
expect(await screen.findByLabelText(/Withdrawal Percentage/i)).toBeInTheDocument();
expect(
await screen.findByRole('spinbutton', { name: /Withdrawal Percentage/i }),
).toBeInTheDocument();
});
});