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
7 changes: 2 additions & 5 deletions src/components/AddExpense/AddExpensePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,16 +172,14 @@ export const AddOrEditExpensePage: React.FC<{
navPromise = async () => router.back();
}

navPromise().catch(console.error);
update((session: any) => ({
...session,
user: {
...(session?.user ?? {}),
currency,
},
}))
.then(() => navPromise())
.then(() => resetState())
.catch(console.error);
})).catch(console.error);
}
}
},
Expand All @@ -206,7 +204,6 @@ export const AddOrEditExpensePage: React.FC<{
expenseDate,
expenseId,
router,
resetState,
addExpenseMutation,
group,
paidBy,
Expand Down
2 changes: 0 additions & 2 deletions src/components/Expense/ConvertibleBalance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,6 @@ export const ConvertibleBalance: React.FC<ConvertibleBalanceProps> = ({
return total;
}, [shouldShowAll, balances, ratesQuery, selectedCurrency, t, setSelectedCurrency]);

console.log(selectedCurrency, groupDefaultCurrency);

if (0 === balances.length) {
return <AmountDisplay className={className} amount={0n} currency="USD" />;
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/currency-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const CurrencyInput: React.FC<
className={cn('text-lg placeholder:text-sm', className)}
inputMode="decimal"
value={strValue}
onFocus={() => onValueChange({ strValue: parseToCleanString(strValue) })}
onFocus={() => onValueChange({ strValue: parseToCleanString(strValue, allowNegative) })}
onBlur={() => {
const formattedValue = format(strValue, { signed: allowNegative, hideSymbol });
return onValueChange({ strValue: formattedValue });
Expand Down
12 changes: 10 additions & 2 deletions src/pages/add.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,15 @@ const AddPage: NextPageWithUser<{
const initializedFriendIdRef = useRef<number | null>(null);
const initializedExpenseIdRef = useRef<string | null>(null);

useEffect(() => () => resetState(), [resetState]);
useEffect(
() => () => {
resetState();
initializedExpenseIdRef.current = null;
initializedGroupIdRef.current = null;
initializedFriendIdRef.current = null;
},
[resetState],
);

// TODO: Set this globally from env var with app router later
const { setMaxUploadFileSizeMB } = useAppStore((s) => s.actions);
Expand Down Expand Up @@ -195,7 +203,7 @@ const AddPage: NextPageWithUser<{
setAmountStr(
getCurrencyHelpersCached(expenseQuery.data.currency).toUIString(
expenseQuery.data.amount,
false,
true,
true,
),
);
Expand Down
50 changes: 50 additions & 0 deletions src/tests/addStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
calculateParticipantSplit,
calculateSplitShareBasedOnAmount,
initSplitShares,
useAddExpenseStore,
} from '~/store/addStore';
import { getCurrencyHelpers } from '~/utils/numbers';

// Mock dependencies
jest.mock('~/utils/array', () => ({
Expand Down Expand Up @@ -1079,3 +1081,51 @@ describe('Function Reversibility Tests', () => {
});
});
});

// Regression test for #658: editing a negative expense must preserve the sign
describe('useAddExpenseStore sign preservation on edit (#658)', () => {
const { toUIString } = getCurrencyHelpers({ locale: 'en-US', currency: 'USD' });
const { actions } = useAddExpenseStore.getState();

beforeEach(() => {
actions.resetState();
});

it('loads a negative expense with the minus sign visible in amountStr', () => {
actions.setAmount(-20000n);
actions.setAmountStr(toUIString(-20000n, true, true));

const state = useAddExpenseStore.getState();
expect(state.amountStr).toBe('-200');
expect(state.isNegative).toBe(true);
expect(state.amount).toBe(20000n);
});

it('preserves the sign when the user edits digits without removing the minus', () => {
actions.setAmount(-20000n);
actions.setAmountStr(toUIString(-20000n, true, true));

// Simulate the user changing -200 to -200.01 via the input
actions.setAmountStr('-200.01');
actions.setAmount(-20001n);

const state = useAddExpenseStore.getState();
expect(state.amountStr).toBe('-200.01');
expect(state.isNegative).toBe(true);
expect(state.amount).toBe(20001n);
});

it('flips the sign when the user deletes the minus and edits the value', () => {
actions.setAmount(-20000n);
actions.setAmountStr(toUIString(-20000n, true, true));

// Simulate the user deleting the minus and changing the value to 200.01
actions.setAmountStr('200.01');
actions.setAmount(20001n);

const state = useAddExpenseStore.getState();
expect(state.amountStr).toBe('200.01');
expect(state.isNegative).toBe(false);
expect(state.amount).toBe(20001n);
});
});
41 changes: 41 additions & 0 deletions src/tests/number.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ describe('getCurrencyHelpers', () => {
])('should format %p as %p with signed flag', (value, expected) => {
expect(toUIString(value, true)).toBe(expected);
});

it.each([
[12345n, '123.45'],
[-12345n, '-123.45'],
[-50n, '-0.5'],
[-0n, '0'],
])('should format %p as %p with signed flag and hideSymbol', (value, expected) => {
expect(toUIString(value, true, true)).toBe(expected);
});
});
describe('JPY (no decimals)', () => {
const currency = 'JPY';
Expand Down Expand Up @@ -173,6 +182,38 @@ describe('getCurrencyHelpers', () => {
expect(sanitizeInput(input, true)).toBe(expected);
});
});

describe('parseToCleanString', () => {
const { parseToCleanString } = getCurrencyHelpers({
locale: 'en-US',
currency: 'USD',
});

it.each([
['-200', '-200'],
['-200.01', '-200.01'],
['--200', '-200'],
['-$200.00', '-200.00'],
])('should keep the minus sign for %p with signed flag', (input, expected) => {
expect(parseToCleanString(input, true)).toBe(expected);
});

it.each([
[-20000n, '-200.00'],
[-12345n, '-123.45'],
[-50n, '-0.50'],
[-0n, '0.00'],
])('should keep the minus sign for bigint %p with signed flag', (value, expected) => {
expect(parseToCleanString(value, true)).toBe(expected);
});

it.each([
['-200', '200'],
['-123.45', '123.45'],
])('should drop the minus sign for %p without signed flag', (input, expected) => {
expect(parseToCleanString(input)).toBe(expected);
});
});
});

describe('currencyConversion', () => {
Expand Down
7 changes: 3 additions & 4 deletions src/utils/numbers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ export const getCurrencyHelpers = ({
return cleaned;
};

const normalizeToMaxLength = (inputString: string) => {
const sanitized = sanitizeInput(inputString);
const normalizeToMaxLength = (inputString: string, signed = false) => {
const sanitized = sanitizeInput(inputString, signed);
const trimmedExceedingDecimals = trimExceedingDecimals(sanitized);
return trimmedExceedingDecimals.endsWith(decimalSeparator)
? trimmedExceedingDecimals.slice(0, -1)
Expand Down Expand Up @@ -173,7 +173,7 @@ export const getCurrencyHelpers = ({
}

if (typeof value === 'string') {
return normalizeToMaxLength(value);
return normalizeToMaxLength(value, signed);
}

return '';
Expand Down Expand Up @@ -233,7 +233,6 @@ export const getCurrencyHelpers = ({
return {
parseToCleanString,
toUIString,
toUIStringSigned: (value: unknown) => toUIString(value, true),
format,
formatter,
sanitizeInput,
Expand Down