From d6d1cc6d6a3f5b6833f0f3e21000b3a1072819ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 20:07:47 +0000 Subject: [PATCH 1/5] Initial plan From 65262c6651c5e120f6eb5690a34f3e6887432309 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 20:10:10 +0000 Subject: [PATCH 2/5] Add test infrastructure and initial tests Co-authored-by: frouaix <876178+frouaix@users.noreply.github.com> --- TESTING.md | 89 ++++++++++++++++++++++ src/tests/bigHelpers.test.ts | 140 +++++++++++++++++++++++++++++++++++ src/tests/setup.ts | 3 + 3 files changed, 232 insertions(+) create mode 100644 TESTING.md create mode 100644 src/tests/bigHelpers.test.ts create mode 100644 src/tests/setup.ts diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..ca5b6c2 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,89 @@ +# Testing Guide + +## Overview +This repository uses [Vitest](https://vitest.dev/) for unit testing, with React Testing Library for component tests. + +## Running Tests + +### Run all tests once +```bash +npm test run +``` + +### Run tests in watch mode +```bash +npm test +``` + +### Run tests with UI +```bash +npm run test:ui +``` + +### Run tests with coverage report +```bash +npm run test:coverage +``` + +## Test Structure + +All tests are located in the `src/tests/` directory: +- `setup.ts` - Test environment setup +- `*.test.ts` - Unit tests for TypeScript modules +- `*.test.tsx` - Component tests for React components + +## Writing Tests + +### Basic Test Example +```typescript +import { describe, it, expect } from 'vitest'; +import { functionToTest } from '../lib/module'; + +describe('Module Name', () => { + describe('functionToTest', () => { + it('should do something specific', () => { + const result = functionToTest(input); + expect(result).toBe(expected); + }); + }); +}); +``` + +### Component Test Example +```typescript +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { MyComponent } from '../components/MyComponent'; + +describe('MyComponent', () => { + it('should render correctly', () => { + render(); + expect(screen.getByText('Expected Text')).toBeInTheDocument(); + }); +}); +``` + +## Test Coverage + +Run `npm run test:coverage` to generate a coverage report. The report will be available in: +- Console output (text summary) +- `coverage/index.html` (detailed HTML report) + +## Current Test Status + +As of the initial setup, we have basic tests for the `bigHelpers` module to demonstrate the testing infrastructure is working correctly. + +## Adding New Tests + +1. Create a new file in `src/tests/` with the naming pattern `*.test.ts` or `*.test.tsx` +2. Import the necessary testing utilities from vitest +3. Write your test cases following the existing patterns +4. Run tests to ensure they pass + +## Best Practices + +- Write descriptive test names that explain what is being tested +- Use `describe` blocks to group related tests +- Test edge cases and error conditions +- Keep tests focused and independent +- Use meaningful assertions diff --git a/src/tests/bigHelpers.test.ts b/src/tests/bigHelpers.test.ts new file mode 100644 index 0000000..a1959ee --- /dev/null +++ b/src/tests/bigHelpers.test.ts @@ -0,0 +1,140 @@ +// Copyright (c) 2026 François Rouaix +import { describe, it, expect } from 'vitest'; +import { + toBig, + add, + subtract, + multiply, + divide, + toCurrency, + toNumber, +} from '../lib/bigHelpers'; + +describe('bigHelpers', () => { + describe('toBig', () => { + it('should convert number to Big', () => { + const result = toBig(100); + expect(result.toString()).toBe('100'); + }); + + it('should convert string to Big', () => { + const result = toBig('100.50'); + expect(result.toString()).toBe('100.5'); + }); + + it('should handle zero', () => { + const result = toBig(0); + expect(result.toString()).toBe('0'); + }); + + it('should handle negative numbers', () => { + const result = toBig(-50.25); + expect(result.toString()).toBe('-50.25'); + }); + }); + + describe('add', () => { + it('should add two Big numbers', () => { + const a = toBig(100); + const b = toBig(50); + const result = add(a, b); + expect(result.toString()).toBe('150'); + }); + + it('should handle decimals precisely', () => { + const a = toBig(0.1); + const b = toBig(0.2); + const result = add(a, b); + expect(result.toString()).toBe('0.3'); + }); + }); + + describe('subtract', () => { + it('should subtract two Big numbers', () => { + const a = toBig(100); + const b = toBig(50); + const result = subtract(a, b); + expect(result.toString()).toBe('50'); + }); + + it('should handle negative results', () => { + const a = toBig(50); + const b = toBig(100); + const result = subtract(a, b); + expect(result.toString()).toBe('-50'); + }); + }); + + describe('multiply', () => { + it('should multiply two Big numbers', () => { + const a = toBig(100); + const b = toBig(2); + const result = multiply(a, b); + expect(result.toString()).toBe('200'); + }); + + it('should handle decimals', () => { + const a = toBig(100); + const b = toBig(0.05); + const result = multiply(a, b); + expect(result.toString()).toBe('5'); + }); + }); + + describe('divide', () => { + it('should divide two Big numbers', () => { + const a = toBig(100); + const b = toBig(2); + const result = divide(a, b); + expect(result.toString()).toBe('50'); + }); + + it('should handle decimal division', () => { + const a = toBig(100); + const b = toBig(3); + const result = divide(a, b); + // Big.js default precision + expect(result.toString()).toContain('33.333333'); + }); + }); + + describe('toCurrency', () => { + it('should format number as currency with default precision', () => { + const value = toBig(1234.567); + const result = toCurrency(value); + expect(result).toBe('1234.57'); + }); + + it('should format with custom precision', () => { + const value = toBig(1234.567); + const result = toCurrency(value, 0); + expect(result).toBe('1235'); + }); + + it('should handle negative values', () => { + const value = toBig(-1234.567); + const result = toCurrency(value); + expect(result).toBe('-1234.57'); + }); + }); + + describe('toNumber', () => { + it('should convert Big to number', () => { + const value = toBig(1234.567); + const result = toNumber(value); + expect(result).toBe(1234.567); + }); + + it('should handle zero', () => { + const value = toBig(0); + const result = toNumber(value); + expect(result).toBe(0); + }); + + it('should handle negative values', () => { + const value = toBig(-1234.567); + const result = toNumber(value); + expect(result).toBe(-1234.567); + }); + }); +}); diff --git a/src/tests/setup.ts b/src/tests/setup.ts new file mode 100644 index 0000000..664ed53 --- /dev/null +++ b/src/tests/setup.ts @@ -0,0 +1,3 @@ +// Copyright (c) 2026 François Rouaix +// Test setup file for vitest +import '@testing-library/jest-dom'; From 21250423a2ef9b5353c6a9672b82278aea1f8374 Mon Sep 17 00:00:00 2001 From: Francois Rouaix <876178+frouaix@users.noreply.github.com> Date: Sat, 14 Feb 2026 12:28:57 -0800 Subject: [PATCH 3/5] Update src/tests/bigHelpers.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Francois Rouaix <876178+frouaix@users.noreply.github.com> --- src/tests/bigHelpers.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/bigHelpers.test.ts b/src/tests/bigHelpers.test.ts index a1959ee..1debb60 100644 --- a/src/tests/bigHelpers.test.ts +++ b/src/tests/bigHelpers.test.ts @@ -93,7 +93,7 @@ describe('bigHelpers', () => { const a = toBig(100); const b = toBig(3); const result = divide(a, b); - // Big.js default precision + // Uses configured Big.js precision (Big.DP = 10 in bigHelpers) expect(result.toString()).toContain('33.333333'); }); }); From 051fcd418429fc281018c2cd2626426328acb69c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 20:30:38 +0000 Subject: [PATCH 4/5] Update TESTING.md to use pnpm and clarify test status Co-authored-by: frouaix <876178+frouaix@users.noreply.github.com> --- TESTING.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/TESTING.md b/TESTING.md index ca5b6c2..012c8f6 100644 --- a/TESTING.md +++ b/TESTING.md @@ -7,22 +7,22 @@ This repository uses [Vitest](https://vitest.dev/) for unit testing, with React ### Run all tests once ```bash -npm test run +pnpm test run ``` ### Run tests in watch mode ```bash -npm test +pnpm test ``` ### Run tests with UI ```bash -npm run test:ui +pnpm run test:ui ``` ### Run tests with coverage report ```bash -npm run test:coverage +pnpm run test:coverage ``` ## Test Structure @@ -65,13 +65,13 @@ describe('MyComponent', () => { ## Test Coverage -Run `npm run test:coverage` to generate a coverage report. The report will be available in: +Run `pnpm run test:coverage` to generate a coverage report. The report will be available in: - Console output (text summary) - `coverage/index.html` (detailed HTML report) ## Current Test Status -As of the initial setup, we have basic tests for the `bigHelpers` module to demonstrate the testing infrastructure is working correctly. +The test infrastructure is now in place with initial tests for the `bigHelpers` module (18 tests). Additional test coverage for other modules needs to be added. The testing framework and conventions are established to facilitate adding comprehensive tests across the codebase. ## Adding New Tests From 06c222ec83f999fe316b399698e9715afbb1594e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 14 Feb 2026 20:33:18 +0000 Subject: [PATCH 5/5] Add comprehensive test coverage for all bigHelpers functions Co-authored-by: frouaix <876178+frouaix@users.noreply.github.com> --- src/tests/bigHelpers.test.ts | 372 +++++++++++++++++++++++++++++++++++ 1 file changed, 372 insertions(+) diff --git a/src/tests/bigHelpers.test.ts b/src/tests/bigHelpers.test.ts index 1debb60..96304f8 100644 --- a/src/tests/bigHelpers.test.ts +++ b/src/tests/bigHelpers.test.ts @@ -8,6 +8,22 @@ import { divide, toCurrency, toNumber, + toDollars, + max, + min, + isZero, + isPositive, + isNegative, + compare, + round, + percent, + sum, + compoundGrowth, + applyGrowth, + parsePercent, + clamp, + effectiveTaxRate, + toPercent, } from '../lib/bigHelpers'; describe('bigHelpers', () => { @@ -137,4 +153,360 @@ describe('bigHelpers', () => { expect(result).toBe(-1234.567); }); }); + + describe('toDollars', () => { + it('should format as integer dollars with no cents', () => { + const value = toBig(1234.567); + const result = toDollars(value); + expect(result).toBe('1235'); + }); + + it('should handle negative values', () => { + const value = toBig(-1234.567); + const result = toDollars(value); + expect(result).toBe('-1235'); + }); + + it('should handle zero', () => { + const value = toBig(0); + const result = toDollars(value); + expect(result).toBe('0'); + }); + }); + + describe('max', () => { + it('should return maximum of multiple values', () => { + const result = max(10, 50, 30, 20); + expect(result.toString()).toBe('50'); + }); + + it('should handle Big values', () => { + const result = max(toBig(10), toBig(50), toBig(30)); + expect(result.toString()).toBe('50'); + }); + + it('should handle negative values', () => { + const result = max(-10, -50, -5); + expect(result.toString()).toBe('-5'); + }); + + it('should return 0 for empty array', () => { + const result = max(); + expect(result.toString()).toBe('0'); + }); + + it('should handle single value', () => { + const result = max(42); + expect(result.toString()).toBe('42'); + }); + }); + + describe('min', () => { + it('should return minimum of multiple values', () => { + const result = min(10, 50, 30, 20); + expect(result.toString()).toBe('10'); + }); + + it('should handle Big values', () => { + const result = min(toBig(10), toBig(50), toBig(5)); + expect(result.toString()).toBe('5'); + }); + + it('should handle negative values', () => { + const result = min(-10, -50, -5); + expect(result.toString()).toBe('-50'); + }); + + it('should return 0 for empty array', () => { + const result = min(); + expect(result.toString()).toBe('0'); + }); + + it('should handle single value', () => { + const result = min(42); + expect(result.toString()).toBe('42'); + }); + }); + + describe('isZero', () => { + it('should return true for zero', () => { + expect(isZero(toBig(0))).toBe(true); + }); + + it('should return false for positive number', () => { + expect(isZero(toBig(1))).toBe(false); + }); + + it('should return false for negative number', () => { + expect(isZero(toBig(-1))).toBe(false); + }); + }); + + describe('isPositive', () => { + it('should return true for positive number', () => { + expect(isPositive(toBig(1))).toBe(true); + }); + + it('should return false for zero', () => { + expect(isPositive(toBig(0))).toBe(false); + }); + + it('should return false for negative number', () => { + expect(isPositive(toBig(-1))).toBe(false); + }); + }); + + describe('isNegative', () => { + it('should return true for negative number', () => { + expect(isNegative(toBig(-1))).toBe(true); + }); + + it('should return false for zero', () => { + expect(isNegative(toBig(0))).toBe(false); + }); + + it('should return false for positive number', () => { + expect(isNegative(toBig(1))).toBe(false); + }); + }); + + describe('compare', () => { + it('should return -1 when a < b', () => { + expect(compare(10, 20)).toBe(-1); + }); + + it('should return 0 when a === b', () => { + expect(compare(20, 20)).toBe(0); + }); + + it('should return 1 when a > b', () => { + expect(compare(30, 20)).toBe(1); + }); + + it('should handle Big values', () => { + expect(compare(toBig(10), toBig(20))).toBe(-1); + expect(compare(toBig(20), toBig(20))).toBe(0); + expect(compare(toBig(30), toBig(20))).toBe(1); + }); + + it('should handle negative values', () => { + expect(compare(-10, -5)).toBe(-1); + expect(compare(-5, -10)).toBe(1); + }); + }); + + describe('round', () => { + it('should round to default 2 decimal places', () => { + const result = round(toBig(1234.5678)); + expect(result.toString()).toBe('1234.57'); + }); + + it('should round to specified decimal places', () => { + const result = round(toBig(1234.5678), 0); + expect(result.toString()).toBe('1235'); + }); + + it('should round to 3 decimal places', () => { + const result = round(toBig(1234.5678), 3); + expect(result.toString()).toBe('1234.568'); + }); + + it('should handle negative values', () => { + const result = round(toBig(-1234.5678)); + expect(result.toString()).toBe('-1234.57'); + }); + }); + + describe('percent', () => { + it('should calculate percentage of a value', () => { + const result = percent(100, 0.15); + expect(result.toString()).toBe('15'); + }); + + it('should handle Big values', () => { + const result = percent(toBig(1000), toBig(0.05)); + expect(result.toString()).toBe('50'); + }); + + it('should handle zero rate', () => { + const result = percent(100, 0); + expect(result.toString()).toBe('0'); + }); + + it('should handle negative rate', () => { + const result = percent(100, -0.1); + expect(result.toString()).toBe('-10'); + }); + }); + + describe('sum', () => { + it('should sum array of Big values', () => { + const values = [toBig(10), toBig(20), toBig(30)]; + const result = sum(values); + expect(result.toString()).toBe('60'); + }); + + it('should handle empty array', () => { + const result = sum([]); + expect(result.toString()).toBe('0'); + }); + + it('should handle single value', () => { + const result = sum([toBig(42)]); + expect(result.toString()).toBe('42'); + }); + + it('should handle negative values', () => { + const values = [toBig(10), toBig(-20), toBig(30)]; + const result = sum(values); + expect(result.toString()).toBe('20'); + }); + }); + + describe('compoundGrowth', () => { + it('should calculate compound growth over periods', () => { + const result = compoundGrowth(1000, 0.05, 3); + expect(result.toString()).toBe('1157.625'); + }); + + it('should handle zero periods', () => { + const result = compoundGrowth(1000, 0.05, 0); + expect(result.toString()).toBe('1000'); + }); + + it('should handle negative rate', () => { + const result = compoundGrowth(1000, -0.1, 2); + expect(result.toString()).toBe('810'); + }); + + it('should handle Big values', () => { + const result = compoundGrowth(toBig(1000), toBig(0.1), 2); + expect(result.toString()).toBe('1210'); + }); + }); + + describe('applyGrowth', () => { + it('should apply growth rate to value', () => { + const result = applyGrowth(1000, 0.05); + expect(result.toString()).toBe('1050'); + }); + + it('should handle zero rate', () => { + const result = applyGrowth(1000, 0); + expect(result.toString()).toBe('1000'); + }); + + it('should handle negative rate', () => { + const result = applyGrowth(1000, -0.1); + expect(result.toString()).toBe('900'); + }); + + it('should handle Big values', () => { + const result = applyGrowth(toBig(1000), toBig(0.05)); + expect(result.toString()).toBe('1050'); + }); + }); + + describe('parsePercent', () => { + it('should parse percentage string with % symbol', () => { + const result = parsePercent('5%'); + expect(result.toString()).toBe('0.05'); + }); + + it('should parse decimal string without % symbol', () => { + const result = parsePercent('0.05'); + expect(result.toString()).toBe('0.05'); + }); + + it('should handle whitespace', () => { + const result = parsePercent(' 5% '); + expect(result.toString()).toBe('0.05'); + }); + + it('should handle large percentages', () => { + const result = parsePercent('150%'); + expect(result.toString()).toBe('1.5'); + }); + + it('should handle decimal percentages', () => { + const result = parsePercent('5.5%'); + expect(result.toString()).toBe('0.055'); + }); + }); + + describe('clamp', () => { + it('should return value when within range', () => { + const result = clamp(50, 0, 100); + expect(result.toString()).toBe('50'); + }); + + it('should return min when value below range', () => { + const result = clamp(-10, 0, 100); + expect(result.toString()).toBe('0'); + }); + + it('should return max when value above range', () => { + const result = clamp(150, 0, 100); + expect(result.toString()).toBe('100'); + }); + + it('should handle Big values', () => { + const result = clamp(toBig(50), toBig(0), toBig(100)); + expect(result.toString()).toBe('50'); + }); + + it('should handle negative ranges', () => { + const result = clamp(-50, -100, -10); + expect(result.toString()).toBe('-50'); + }); + }); + + describe('effectiveTaxRate', () => { + it('should calculate effective tax rate', () => { + const result = effectiveTaxRate(15000, 100000); + expect(result.toString()).toBe('0.15'); + }); + + it('should return 0 when income is 0', () => { + const result = effectiveTaxRate(1000, 0); + expect(result.toString()).toBe('0'); + }); + + it('should handle Big values', () => { + const result = effectiveTaxRate(toBig(20000), toBig(100000)); + expect(result.toString()).toBe('0.2'); + }); + + it('should handle zero tax', () => { + const result = effectiveTaxRate(0, 100000); + expect(result.toString()).toBe('0'); + }); + }); + + describe('toPercent', () => { + it('should format Big as percentage with default precision', () => { + const result = toPercent(toBig(0.05)); + expect(result).toBe('5.00%'); + }); + + it('should format with custom precision', () => { + const result = toPercent(toBig(0.05), 0); + expect(result).toBe('5%'); + }); + + it('should handle large percentages', () => { + const result = toPercent(toBig(1.5)); + expect(result).toBe('150.00%'); + }); + + it('should handle negative percentages', () => { + const result = toPercent(toBig(-0.05)); + expect(result).toBe('-5.00%'); + }); + + it('should handle zero', () => { + const result = toPercent(toBig(0)); + expect(result).toBe('0.00%'); + }); + }); });