Files
app.zeusln.zeus/utils/KeypadUtils.test.ts

724 lines
24 KiB
TypeScript

import {
getDecimalLimit,
validateKeypadInput,
getAmountFontSize,
deleteLastCharacter,
parseClipboardAmount
} from './KeypadUtils';
// Mock stores
const createMockFiatStore = (decimalPlaces?: number) => ({
symbolLookup: jest
.fn()
.mockReturnValue(
decimalPlaces !== undefined ? { decimalPlaces } : undefined
),
getSymbol: jest.fn().mockReturnValue({ separatorSwap: false })
});
const createMockFiatStoreWithSwap = (
decimalPlaces: number = 2,
separatorSwap: boolean = true
) => ({
symbolLookup: jest.fn().mockReturnValue({ decimalPlaces }),
getSymbol: jest.fn().mockReturnValue({ separatorSwap })
});
const createMockSettingsStore = (fiat: string = 'USD') => ({
settings: { fiat }
});
describe('KeypadUtils', () => {
describe('getDecimalLimit', () => {
it('returns 3 for sats', () => {
const fiatStore = createMockFiatStore();
const settingsStore = createMockSettingsStore();
expect(
getDecimalLimit('sats', fiatStore as any, settingsStore as any)
).toBe(3);
});
it('returns 8 for BTC', () => {
const fiatStore = createMockFiatStore();
const settingsStore = createMockSettingsStore();
expect(
getDecimalLimit('BTC', fiatStore as any, settingsStore as any)
).toBe(8);
});
it('returns fiat decimal places from store', () => {
const fiatStore = createMockFiatStore(2);
const settingsStore = createMockSettingsStore('USD');
expect(
getDecimalLimit('fiat', fiatStore as any, settingsStore as any)
).toBe(2);
});
it('returns 0 decimal places for currencies like JPY', () => {
const fiatStore = createMockFiatStore(0);
const settingsStore = createMockSettingsStore('JPY');
expect(
getDecimalLimit('fiat', fiatStore as any, settingsStore as any)
).toBe(0);
});
it('defaults to 2 decimal places when fiat properties not found', () => {
const fiatStore = createMockFiatStore(undefined);
const settingsStore = createMockSettingsStore('UNKNOWN');
expect(
getDecimalLimit('fiat', fiatStore as any, settingsStore as any)
).toBe(2);
});
it('returns null for unknown units', () => {
const fiatStore = createMockFiatStore();
const settingsStore = createMockSettingsStore();
expect(
getDecimalLimit(
'unknown',
fiatStore as any,
settingsStore as any
)
).toBeNull();
});
});
describe('validateKeypadInput', () => {
const fiatStore = createMockFiatStore(2);
const settingsStore = createMockSettingsStore();
describe('basic input', () => {
it('accepts valid digit input', () => {
const result = validateKeypadInput(
'0',
'5',
'sats',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({ valid: true, newAmount: '5' });
});
it('appends digit to existing amount', () => {
const result = validateKeypadInput(
'12',
'3',
'sats',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({ valid: true, newAmount: '123' });
});
it('accepts decimal point', () => {
const result = validateKeypadInput(
'5',
'.',
'sats',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({ valid: true, newAmount: '5.' });
});
it('replaces leading zero with digit', () => {
const result = validateKeypadInput(
'0',
'7',
'sats',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({ valid: true, newAmount: '7' });
});
it('handles decimal point when amount is zero', () => {
const result = validateKeypadInput(
'0',
'.',
'sats',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({ valid: true, newAmount: '0.' });
});
});
describe('decimal place limits', () => {
it('rejects input exceeding sats decimal limit (3)', () => {
const result = validateKeypadInput(
'1.234',
'5',
'sats',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({ valid: false, newAmount: '1.234' });
});
it('accepts input within sats decimal limit', () => {
const result = validateKeypadInput(
'1.23',
'4',
'sats',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({ valid: true, newAmount: '1.234' });
});
it('rejects input exceeding BTC decimal limit (8)', () => {
const result = validateKeypadInput(
'1.12345678',
'9',
'BTC',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({
valid: false,
newAmount: '1.12345678'
});
});
it('accepts input within BTC decimal limit', () => {
const result = validateKeypadInput(
'1.1234567',
'8',
'BTC',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({
valid: true,
newAmount: '1.12345678'
});
});
it('rejects input exceeding fiat decimal limit (2)', () => {
const result = validateKeypadInput(
'10.99',
'9',
'fiat',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({ valid: false, newAmount: '10.99' });
});
});
describe('decimal point rules', () => {
it('rejects second decimal point', () => {
const result = validateKeypadInput(
'1.5',
'.',
'sats',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({ valid: false, newAmount: '1.5' });
});
it('rejects decimal for zero-decimal currencies', () => {
const jpyFiatStore = createMockFiatStore(0);
const result = validateKeypadInput(
'100',
'.',
'fiat',
jpyFiatStore as any,
settingsStore as any
);
expect(result).toEqual({ valid: false, newAmount: '100' });
});
});
describe('sats integer limit', () => {
it('rejects more than 12 integer digits for sats', () => {
const result = validateKeypadInput(
'123456789012',
'3',
'sats',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({
valid: false,
newAmount: '123456789012'
});
});
it('allows decimal point after 12 integer digits for sats', () => {
const result = validateKeypadInput(
'123456789012',
'.',
'sats',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({
valid: true,
newAmount: '123456789012.'
});
});
it('accepts 12th integer digit for sats', () => {
const result = validateKeypadInput(
'12345678901',
'2',
'sats',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({
valid: true,
newAmount: '123456789012'
});
});
});
describe('BTC integer limit', () => {
it('rejects more than 8 integer digits for BTC', () => {
const result = validateKeypadInput(
'12345678',
'9',
'BTC',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({ valid: false, newAmount: '12345678' });
});
it('allows decimal point after 8 integer digits for BTC', () => {
const result = validateKeypadInput(
'12345678',
'.',
'BTC',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({ valid: true, newAmount: '12345678.' });
});
it('allows digits after decimal with 8 integer digits', () => {
const result = validateKeypadInput(
'12345678.',
'1',
'BTC',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({
valid: true,
newAmount: '12345678.1'
});
});
});
describe('fiat integer limit', () => {
it('rejects more than 10 integer digits for fiat', () => {
const result = validateKeypadInput(
'1234567890',
'1',
'fiat',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({
valid: false,
newAmount: '1234567890'
});
});
it('allows decimal point after 10 integer digits for fiat', () => {
const result = validateKeypadInput(
'1234567890',
'.',
'fiat',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({
valid: true,
newAmount: '1234567890.'
});
});
it('allows digits after decimal with 10 integer digits for fiat', () => {
const result = validateKeypadInput(
'1234567890.',
'1',
'fiat',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({
valid: true,
newAmount: '1234567890.1'
});
});
it('accepts 10th integer digit for fiat', () => {
const result = validateKeypadInput(
'123456789',
'0',
'fiat',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({
valid: true,
newAmount: '1234567890'
});
});
});
describe('capacity limits', () => {
it('rejects BTC amount exceeding 21 million', () => {
const result = validateKeypadInput(
'21000000',
'1',
'BTC',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({ valid: false, newAmount: '21000000' });
});
it('accepts BTC amount at exactly 21 million', () => {
const result = validateKeypadInput(
'2100000',
'0',
'BTC',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({ valid: true, newAmount: '21000000' });
});
it('rejects sats amount exceeding max supply', () => {
const result = validateKeypadInput(
'2100000000000000',
'1',
'sats',
fiatStore as any,
settingsStore as any
);
expect(result).toEqual({
valid: false,
newAmount: '2100000000000000'
});
});
});
});
describe('getAmountFontSize', () => {
describe('standard sizing', () => {
it('returns 80 for short amounts (1-4 chars)', () => {
expect(getAmountFontSize(1, 0)).toBe(80);
expect(getAmountFontSize(2, 0)).toBe(80);
expect(getAmountFontSize(3, 0)).toBe(80);
expect(getAmountFontSize(4, 0)).toBe(80);
});
it('returns progressively smaller sizes for longer amounts', () => {
expect(getAmountFontSize(5, 0)).toBe(75);
expect(getAmountFontSize(6, 0)).toBe(65);
expect(getAmountFontSize(7, 0)).toBe(60);
expect(getAmountFontSize(8, 0)).toBe(55);
expect(getAmountFontSize(9, 0)).toBe(50);
expect(getAmountFontSize(10, 0)).toBe(45);
});
it('includes placeholder count in total length', () => {
expect(getAmountFontSize(3, 2)).toBe(75); // 3 + 2 = 5
expect(getAmountFontSize(2, 4)).toBe(65); // 2 + 4 = 6
});
});
describe('compact sizing', () => {
it('returns 80 for short amounts (1-2 chars)', () => {
expect(getAmountFontSize(1, 0, { compact: true })).toBe(80);
expect(getAmountFontSize(2, 0, { compact: true })).toBe(80);
});
it('returns different sizes than standard mode', () => {
expect(getAmountFontSize(3, 0, { compact: true })).toBe(65);
expect(getAmountFontSize(4, 0, { compact: true })).toBe(65);
expect(getAmountFontSize(5, 0, { compact: true })).toBe(55);
expect(getAmountFontSize(6, 0, { compact: true })).toBe(55);
expect(getAmountFontSize(7, 0, { compact: true })).toBe(50);
expect(getAmountFontSize(8, 0, { compact: true })).toBe(45);
expect(getAmountFontSize(10, 0, { compact: true })).toBe(35);
});
});
describe('needInbound sizing', () => {
it('returns larger size when not needing inbound', () => {
expect(getAmountFontSize(4, 0)).toBe(80);
expect(getAmountFontSize(4, 0, { needInbound: true })).toBe(70);
});
it('returns smaller sizes when needing inbound', () => {
expect(getAmountFontSize(5, 0)).toBe(75);
expect(getAmountFontSize(5, 0, { needInbound: true })).toBe(65);
expect(getAmountFontSize(6, 0)).toBe(65);
expect(getAmountFontSize(6, 0, { needInbound: true })).toBe(60);
expect(getAmountFontSize(7, 0)).toBe(60);
expect(getAmountFontSize(7, 0, { needInbound: true })).toBe(55);
expect(getAmountFontSize(8, 0)).toBe(55);
expect(getAmountFontSize(8, 0, { needInbound: true })).toBe(50);
expect(getAmountFontSize(9, 0)).toBe(50);
expect(getAmountFontSize(9, 0, { needInbound: true })).toBe(45);
expect(getAmountFontSize(10, 0)).toBe(45);
expect(getAmountFontSize(10, 0, { needInbound: true })).toBe(
40
);
});
});
});
describe('parseClipboardAmount', () => {
const settingsStore = createMockSettingsStore();
it('returns null for empty input', () => {
const fiatStore = createMockFiatStore();
expect(
parseClipboardAmount(
'',
'sats',
fiatStore as any,
settingsStore as any
)
).toBeNull();
});
it('parses a plain integer', () => {
const fiatStore = createMockFiatStore();
expect(
parseClipboardAmount(
'12345',
'sats',
fiatStore as any,
settingsStore as any
)
).toBe('12345');
});
it('strips US-style thousands separators', () => {
const fiatStore = createMockFiatStore();
expect(
parseClipboardAmount(
'1,234,567',
'sats',
fiatStore as any,
settingsStore as any
)
).toBe('1234567');
});
it('strips currency symbols and whitespace', () => {
const fiatStore = createMockFiatStore(2);
expect(
parseClipboardAmount(
'$ 19.99',
'fiat',
fiatStore as any,
settingsStore as any
)
).toBe('19.99');
});
it('strips unit suffix labels', () => {
const fiatStore = createMockFiatStore();
expect(
parseClipboardAmount(
'50000 sats',
'sats',
fiatStore as any,
settingsStore as any
)
).toBe('50000');
});
it('strips the active fiat currency code', () => {
const fiatStore = createMockFiatStore(2);
expect(
parseClipboardAmount(
'100 USD',
'fiat',
fiatStore as any,
createMockSettingsStore('USD') as any
)
).toBe('100');
});
it('rejects non-active fiat currency codes', () => {
const fiatStore = createMockFiatStore(2);
expect(
parseClipboardAmount(
'50 EUR',
'fiat',
fiatStore as any,
createMockSettingsStore('USD') as any
)
).toBeNull();
});
it('honours comma decimal separator for fiat with separatorSwap', () => {
const fiatStore = createMockFiatStoreWithSwap(2, true);
expect(
parseClipboardAmount(
'1.234,56',
'fiat',
fiatStore as any,
settingsStore as any
)
).toBe('1234.56');
});
it('truncates excess decimal places to unit limit', () => {
const fiatStore = createMockFiatStore();
expect(
parseClipboardAmount(
'0.123456789',
'BTC',
fiatStore as any,
settingsStore as any
)
).toBe('0.12345678');
});
it('truncates excess decimal places for sats', () => {
const fiatStore = createMockFiatStore();
expect(
parseClipboardAmount(
'5.123456',
'sats',
fiatStore as any,
settingsStore as any
)
).toBe('5.123');
});
it('drops decimals entirely for zero-decimal fiat (JPY)', () => {
const fiatStore = createMockFiatStore(0);
expect(
parseClipboardAmount(
'500.99',
'fiat',
fiatStore as any,
settingsStore as any
)
).toBe('500');
});
it('rejects amounts exceeding BTC integer length', () => {
const fiatStore = createMockFiatStore();
expect(
parseClipboardAmount(
'123456789',
'BTC',
fiatStore as any,
settingsStore as any
)
).toBeNull();
});
it('rejects amounts exceeding sats max supply', () => {
const fiatStore = createMockFiatStore();
expect(
parseClipboardAmount(
'2100000000000001',
'sats',
fiatStore as any,
settingsStore as any
)
).toBeNull();
});
it('rejects BOLT11 invoices', () => {
const fiatStore = createMockFiatStore();
expect(
parseClipboardAmount(
'lnbc1500u1p3xnpsapp5q...',
'sats',
fiatStore as any,
settingsStore as any
)
).toBeNull();
});
it('rejects on-chain addresses', () => {
const fiatStore = createMockFiatStore();
expect(
parseClipboardAmount(
'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4',
'sats',
fiatStore as any,
settingsStore as any
)
).toBeNull();
});
it('rejects non-numeric garbage', () => {
const fiatStore = createMockFiatStore();
expect(
parseClipboardAmount(
'hello world',
'sats',
fiatStore as any,
settingsStore as any
)
).toBeNull();
});
it('normalizes leading zeros', () => {
const fiatStore = createMockFiatStore();
expect(
parseClipboardAmount(
'0000123',
'sats',
fiatStore as any,
settingsStore as any
)
).toBe('123');
});
it('handles bare decimal like ".5"', () => {
const fiatStore = createMockFiatStore();
expect(
parseClipboardAmount(
'.5',
'BTC',
fiatStore as any,
settingsStore as any
)
).toBe('0.5');
});
});
describe('deleteLastCharacter', () => {
it('returns "0" for single character amount', () => {
expect(deleteLastCharacter('5')).toBe('0');
expect(deleteLastCharacter('0')).toBe('0');
});
it('removes last character from multi-character amount', () => {
expect(deleteLastCharacter('123')).toBe('12');
expect(deleteLastCharacter('10')).toBe('1');
expect(deleteLastCharacter('1.5')).toBe('1.');
expect(deleteLastCharacter('1.')).toBe('1');
});
it('handles decimal amounts correctly', () => {
expect(deleteLastCharacter('12.34')).toBe('12.3');
expect(deleteLastCharacter('0.1')).toBe('0.');
});
});
});