@ledgerhq/lumen-utils-shared 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +32 -0
  2. package/dist/index.d.ts +8 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +115 -0
  5. package/dist/lib/cn.d.ts +3 -0
  6. package/dist/lib/cn.d.ts.map +1 -0
  7. package/dist/lib/create-safe-context/create-safe-context.d.ts +9 -0
  8. package/dist/lib/create-safe-context/create-safe-context.d.ts.map +1 -0
  9. package/dist/lib/create-safe-context/index.d.ts +2 -0
  10. package/dist/lib/create-safe-context/index.d.ts.map +1 -0
  11. package/dist/lib/get-object-path/get-object-path.d.ts +18 -0
  12. package/dist/lib/get-object-path/get-object-path.d.ts.map +1 -0
  13. package/dist/lib/get-object-path/index.d.ts +2 -0
  14. package/dist/lib/get-object-path/index.d.ts.map +1 -0
  15. package/dist/lib/input-formatter/font-size-formatter.d.ts +6 -0
  16. package/dist/lib/input-formatter/font-size-formatter.d.ts.map +1 -0
  17. package/dist/lib/input-formatter/format-thousands.d.ts +18 -0
  18. package/dist/lib/input-formatter/format-thousands.d.ts.map +1 -0
  19. package/dist/lib/input-formatter/index.d.ts +4 -0
  20. package/dist/lib/input-formatter/index.d.ts.map +1 -0
  21. package/dist/lib/input-formatter/text-formatter.d.ts +31 -0
  22. package/dist/lib/input-formatter/text-formatter.d.ts.map +1 -0
  23. package/dist/lib/is-text-children/index.d.ts +2 -0
  24. package/dist/lib/is-text-children/index.d.ts.map +1 -0
  25. package/dist/lib/is-text-children/is-text-children.d.ts +3 -0
  26. package/dist/lib/is-text-children/is-text-children.d.ts.map +1 -0
  27. package/dist/lib/string-utils/index.d.ts +2 -0
  28. package/dist/lib/string-utils/index.d.ts.map +1 -0
  29. package/dist/lib/string-utils/string-utils.d.ts +6 -0
  30. package/dist/lib/string-utils/string-utils.d.ts.map +1 -0
  31. package/dist/lib/use-merge-ref/index.d.ts +2 -0
  32. package/dist/lib/use-merge-ref/index.d.ts.map +1 -0
  33. package/dist/lib/use-merge-ref/use-merge-ref.d.ts +8 -0
  34. package/dist/lib/use-merge-ref/use-merge-ref.d.ts.map +1 -0
  35. package/eslint.config.mjs +22 -0
  36. package/package.json +28 -0
  37. package/project.json +7 -0
  38. package/src/index.ts +7 -0
  39. package/src/lib/cn.ts +6 -0
  40. package/src/lib/create-safe-context/create-safe-context.test.tsx +69 -0
  41. package/src/lib/create-safe-context/create-safe-context.tsx +46 -0
  42. package/src/lib/create-safe-context/index.ts +1 -0
  43. package/src/lib/get-object-path/get-object-path.test.ts +72 -0
  44. package/src/lib/get-object-path/get-object-path.ts +31 -0
  45. package/src/lib/get-object-path/index.ts +1 -0
  46. package/src/lib/input-formatter/font-size-formatter.ts +16 -0
  47. package/src/lib/input-formatter/format-thousands.test.ts +36 -0
  48. package/src/lib/input-formatter/format-thousands.ts +34 -0
  49. package/src/lib/input-formatter/index.ts +3 -0
  50. package/src/lib/input-formatter/text-formatter.test.ts +503 -0
  51. package/src/lib/input-formatter/text-formatter.ts +95 -0
  52. package/src/lib/is-text-children/index.ts +1 -0
  53. package/src/lib/is-text-children/is-text-children.test.ts +33 -0
  54. package/src/lib/is-text-children/is-text-children.ts +9 -0
  55. package/src/lib/string-utils/index.ts +1 -0
  56. package/src/lib/string-utils/string-utils.test.ts +85 -0
  57. package/src/lib/string-utils/string-utils.ts +11 -0
  58. package/src/lib/use-merge-ref/index.ts +1 -0
  59. package/src/lib/use-merge-ref/use-merge-ref.ts +41 -0
  60. package/tsconfig.json +13 -0
  61. package/tsconfig.lib.json +29 -0
  62. package/tsconfig.spec.json +37 -0
  63. package/vite.config.ts +40 -0
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Access a nested property in an object using an array path.
3
+ *
4
+ * @param obj - The object to access
5
+ * @param path - Array of keys representing the path to the value
6
+ * @returns The value at the path, or undefined if not found
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const theme = { colors: { bg: { surface: '#fff' } } };
11
+ *
12
+ * getObjectPath(theme, ['colors', 'bg', 'surface']); // '#fff'
13
+ * getObjectPath(theme, ['colors', 'bg']); // { surface: '#fff' }
14
+ * getObjectPath(theme, ['spacings']); // undefined
15
+ * ```
16
+ */
17
+ export const getObjectPath = <T = unknown>(
18
+ obj: Record<string, unknown>,
19
+ path: string[],
20
+ ): T | undefined => {
21
+ let current: unknown = obj;
22
+
23
+ for (const key of path) {
24
+ if (current === null || current === undefined) {
25
+ return undefined;
26
+ }
27
+ current = (current as Record<string, unknown>)[key];
28
+ }
29
+
30
+ return current as T | undefined;
31
+ };
@@ -0,0 +1 @@
1
+ export { getObjectPath } from './get-object-path';
@@ -0,0 +1,16 @@
1
+ const MAX_FONT_SIZE = 48;
2
+ const MIN_FONT_SIZE = 17;
3
+ const SCALE_FACTOR = 2;
4
+
5
+ /**
6
+ * Calculates the font size based on the number of digits in the input value.
7
+ * Scales from 48px (max) to 17px (min) as digit count increases.
8
+ */
9
+ export function getFontSize(value: string): number {
10
+ const digits = value.replace(/\D/g, '').length;
11
+ const fontSize = Math.max(
12
+ MIN_FONT_SIZE,
13
+ MAX_FONT_SIZE - digits * SCALE_FACTOR,
14
+ );
15
+ return fontSize;
16
+ }
@@ -0,0 +1,36 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { formatThousands } from './format-thousands';
3
+
4
+ describe('formatThousands', () => {
5
+ it('should format thousands with spaces', () => {
6
+ expect(formatThousands('1000')).toBe('1 000');
7
+ expect(formatThousands('1000000')).toBe('1 000 000');
8
+ });
9
+
10
+ it('should preserve decimal places', () => {
11
+ expect(formatThousands('1234.5678')).toBe('1 234.5678');
12
+ expect(formatThousands('1000000.123')).toBe('1 000 000.123');
13
+ });
14
+
15
+ it('should handle trailing decimal point', () => {
16
+ expect(formatThousands('1000.')).toBe('1 000.');
17
+ });
18
+
19
+ it('should return empty string for empty input', () => {
20
+ expect(formatThousands('')).toBe('');
21
+ });
22
+
23
+ it('should handle numbers without thousands', () => {
24
+ expect(formatThousands('123')).toBe('123');
25
+ expect(formatThousands('99')).toBe('99');
26
+ });
27
+
28
+ it('should format with preserveDecimals=false', () => {
29
+ expect(formatThousands('1234.5678', false)).toBe('1 234');
30
+ });
31
+
32
+ it('should handle large numbers', () => {
33
+ expect(formatThousands('123456789')).toBe('123 456 789');
34
+ expect(formatThousands('1234567890123')).toBe('1 234 567 890 123');
35
+ });
36
+ });
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Formats a number string with space-separated thousands.
3
+ * e.g., '1000000' -> '1 000 000'
4
+ *
5
+ * @returns Formatted string with space-separated thousands
6
+ *
7
+ * @example
8
+ * formatThousands('1000') // '1 000'
9
+ * formatThousands('1000000') // '1 000 000'
10
+ * formatThousands('1234.5678') // '1 234.5678'
11
+ * formatThousands('1234.5678', false) // '1 235'
12
+ */
13
+ export function formatThousands(
14
+ /** The value to format */
15
+ value: string,
16
+ /** Whether to preserve decimal places */
17
+ preserveDecimals = true,
18
+ ): string {
19
+ if (!value) return '';
20
+
21
+ const hasDecimalPoint = value.includes('.');
22
+ const [integerPart, decimalPart] = value.split('.');
23
+
24
+ // Add space separator every 3 digits
25
+ const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
26
+
27
+ if (hasDecimalPoint && preserveDecimals) {
28
+ return decimalPart
29
+ ? `${formattedInteger}.${decimalPart}`
30
+ : `${formattedInteger}.`;
31
+ }
32
+
33
+ return formattedInteger;
34
+ }
@@ -0,0 +1,3 @@
1
+ export { getFontSize } from './font-size-formatter';
2
+ export { formatThousands } from './format-thousands';
3
+ export { textFormatter } from './text-formatter';
@@ -0,0 +1,503 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { textFormatter } from './text-formatter.js';
3
+
4
+ describe('textFormatter', () => {
5
+ describe('decimal handling with allowDecimals=true (default)', () => {
6
+ type Case = { name: string; input: string; expected: string };
7
+
8
+ const decimalCases: Case[] = [
9
+ { name: 'convert single dot to "0."', input: '.', expected: '0.' },
10
+ { name: 'prepend 0 to .5', input: '.5', expected: '0.5' },
11
+ { name: 'prepend 0 to .123', input: '.123', expected: '0.123' },
12
+ { name: 'allow 1.5', input: '1.5', expected: '1.5' },
13
+ { name: 'allow 123.456', input: '123.456', expected: '123.456' },
14
+ { name: 'allow 0.5', input: '0.5', expected: '0.5' },
15
+ {
16
+ name: 'limit to 9 decimal places (1.1234567899)',
17
+ input: '1.1234567899',
18
+ expected: '1.123456789',
19
+ },
20
+ {
21
+ name: 'limit to 9 decimal places (0.123456789012345)',
22
+ input: '0.123456789012345',
23
+ expected: '0.123456789',
24
+ },
25
+ ];
26
+
27
+ it.each(decimalCases)('$name', ({ input, expected }) => {
28
+ expect(textFormatter(input)).toBe(expected);
29
+ });
30
+
31
+ const multipleDecimalCases: Case[] = [
32
+ {
33
+ name: 'remove multiple decimal points (1.2.3)',
34
+ input: '1.2.3',
35
+ expected: '1.23',
36
+ },
37
+ {
38
+ name: 'remove multiple decimal points (1.2.3.4)',
39
+ input: '1.2.3.4',
40
+ expected: '1.234',
41
+ },
42
+ { name: 'handle triple dots', input: '...', expected: '0.' },
43
+ { name: 'handle 1..2', input: '1..2', expected: '1.2' },
44
+ ];
45
+
46
+ it.each(multipleDecimalCases)('$name', ({ input, expected }) => {
47
+ expect(textFormatter(input)).toBe(expected);
48
+ });
49
+ });
50
+
51
+ describe('leading zero handling', () => {
52
+ type Case = { name: string; input: string; expected: string };
53
+
54
+ const leadingZeroCases: Case[] = [
55
+ { name: 'remove leading zeros from 01', input: '01', expected: '1' },
56
+ { name: 'remove leading zeros from 001', input: '001', expected: '1' },
57
+ {
58
+ name: 'remove leading zeros from 0123',
59
+ input: '0123',
60
+ expected: '123',
61
+ },
62
+ { name: 'preserve single zero', input: '0', expected: '0' },
63
+ { name: 'convert 000 to 0', input: '000', expected: '0' },
64
+ { name: 'preserve zero in 0.5', input: '0.5', expected: '0.5' },
65
+ { name: 'preserve zero in 0.0', input: '0.0', expected: '0.0' },
66
+ { name: 'preserve zero in 0.123', input: '0.123', expected: '0.123' },
67
+ { name: 'handle 01.5', input: '01.5', expected: '1.5' },
68
+ { name: 'handle 001.23', input: '001.23', expected: '1.23' },
69
+ ];
70
+
71
+ it.each(leadingZeroCases)('$name', ({ input, expected }) => {
72
+ expect(textFormatter(input)).toBe(expected);
73
+ });
74
+ });
75
+
76
+ describe('comma to dot conversion', () => {
77
+ type Case = { name: string; input: string; expected: string };
78
+
79
+ const commaCases: Case[] = [
80
+ { name: 'convert 1,5 to 1.5', input: '1,5', expected: '1.5' },
81
+ { name: 'convert 12,34 to 12.34', input: '12,34', expected: '12.34' },
82
+ { name: 'convert ,5 to 0.5', input: ',5', expected: '0.5' },
83
+ {
84
+ name: 'handle multiple commas (1,2,3)',
85
+ input: '1,2,3',
86
+ expected: '1.23',
87
+ },
88
+ { name: 'handle triple commas', input: ',,,', expected: '0.' },
89
+ ];
90
+
91
+ it.each(commaCases)('$name', ({ input, expected }) => {
92
+ expect(textFormatter(input)).toBe(expected);
93
+ });
94
+ });
95
+
96
+ describe('non-numeric character removal', () => {
97
+ type Case = { name: string; input: string; expected: string };
98
+
99
+ const nonNumericCases: Case[] = [
100
+ {
101
+ name: 'remove letters from abc123def',
102
+ input: 'abc123def',
103
+ expected: '123',
104
+ },
105
+ { name: 'remove $ from $100.50', input: '$100.50', expected: '100.50' },
106
+ {
107
+ name: 'remove special chars from 1@2#3$',
108
+ input: '1@2#3$',
109
+ expected: '123',
110
+ },
111
+ {
112
+ name: 'preserve only digits and dots (1a2b.3c4d)',
113
+ input: '1a2b.3c4d',
114
+ expected: '12.34',
115
+ },
116
+ {
117
+ name: 'remove special chars (!@#1.23$%^)',
118
+ input: '!@#1.23$%^',
119
+ expected: '1.23',
120
+ },
121
+ ];
122
+
123
+ it.each(nonNumericCases)('$name', ({ input, expected }) => {
124
+ expect(textFormatter(input)).toBe(expected);
125
+ });
126
+ });
127
+
128
+ describe('integer-only mode with allowDecimals=false', () => {
129
+ type Case = {
130
+ name: string;
131
+ input: string;
132
+ expected: string;
133
+ options?: { allowDecimals: boolean };
134
+ };
135
+
136
+ const integerOnlyCases: Case[] = [
137
+ {
138
+ name: 'remove decimal from 1.5',
139
+ input: '1.5',
140
+ expected: '15',
141
+ options: { allowDecimals: false },
142
+ },
143
+ {
144
+ name: 'remove decimal from 123.456',
145
+ input: '123.456',
146
+ expected: '123 456',
147
+ options: { allowDecimals: false },
148
+ },
149
+ {
150
+ name: 'remove decimal from .5',
151
+ input: '.5',
152
+ expected: '5',
153
+ options: { allowDecimals: false },
154
+ },
155
+ {
156
+ name: 'remove non-digits from abc123def',
157
+ input: 'abc123def',
158
+ expected: '123',
159
+ options: { allowDecimals: false },
160
+ },
161
+ {
162
+ name: 'remove $ from $100',
163
+ input: '$100',
164
+ expected: '100',
165
+ options: { allowDecimals: false },
166
+ },
167
+ {
168
+ name: 'remove commas from 1,2,3',
169
+ input: '1,2,3',
170
+ expected: '123',
171
+ options: { allowDecimals: false },
172
+ },
173
+ {
174
+ name: 'handle leading zeros in 01',
175
+ input: '01',
176
+ expected: '1',
177
+ options: { allowDecimals: false },
178
+ },
179
+ {
180
+ name: 'handle leading zeros in 001',
181
+ input: '001',
182
+ expected: '1',
183
+ options: { allowDecimals: false },
184
+ },
185
+ {
186
+ name: 'convert 000 to 0',
187
+ input: '000',
188
+ expected: '0',
189
+ options: { allowDecimals: false },
190
+ },
191
+ ];
192
+
193
+ it.each(integerOnlyCases)('$name', ({ input, expected, options }) => {
194
+ expect(textFormatter(input, options)).toBe(expected);
195
+ });
196
+ });
197
+
198
+ describe('edge cases and empty input', () => {
199
+ type Case = { name: string; input: string; expected: string };
200
+
201
+ const edgeCases: Case[] = [
202
+ { name: 'handle empty string', input: '', expected: '' },
203
+ {
204
+ name: 'handle strings with no digits (abc)',
205
+ input: 'abc',
206
+ expected: '',
207
+ },
208
+ {
209
+ name: 'handle strings with no digits (!@#$%^)',
210
+ input: '!@#$%^',
211
+ expected: '',
212
+ },
213
+ {
214
+ name: 'handle whitespace ( 1 2 3 )',
215
+ input: ' 1 2 3 ',
216
+ expected: '123',
217
+ },
218
+ { name: 'handle whitespace ( 1.5 )', input: ' 1.5 ', expected: '1.5' },
219
+ ];
220
+
221
+ it.each(edgeCases)('$name', ({ input, expected }) => {
222
+ expect(textFormatter(input)).toBe(expected);
223
+ });
224
+ });
225
+
226
+ describe('length limits with maxIntegerLength and maxDecimalLength', () => {
227
+ describe('integer length limits', () => {
228
+ type Case = {
229
+ name: string;
230
+ input: string;
231
+ expected: string;
232
+ options?: {
233
+ thousandsSeparator?: boolean;
234
+ maxIntegerLength?: number;
235
+ };
236
+ };
237
+
238
+ const integerLimitCases: Case[] = [
239
+ {
240
+ name: 'limit to 5 digits (123456789)',
241
+ input: '123456789',
242
+ expected: '12345',
243
+ options: { thousandsSeparator: false, maxIntegerLength: 5 },
244
+ },
245
+ {
246
+ name: 'limit to 3 digits (987654321)',
247
+ input: '987654321',
248
+ expected: '987',
249
+ options: { thousandsSeparator: false, maxIntegerLength: 3 },
250
+ },
251
+ {
252
+ name: 'truncate 10th digit (1000000000)',
253
+ input: '1000000000',
254
+ expected: '100000000',
255
+ options: { thousandsSeparator: false },
256
+ },
257
+ {
258
+ name: 'allow exactly 9 digits',
259
+ input: '123456789',
260
+ expected: '123456789',
261
+ options: { thousandsSeparator: false },
262
+ },
263
+ {
264
+ name: 'allow exactly maxIntegerLength digits',
265
+ input: '12345',
266
+ expected: '12345',
267
+ options: { thousandsSeparator: false, maxIntegerLength: 5 },
268
+ },
269
+ {
270
+ name: 'handle limits with thousands separator (123456789)',
271
+ input: '123456789',
272
+ expected: '123 456 789',
273
+ },
274
+ {
275
+ name: 'truncate 10th digit with thousands separator',
276
+ input: '1234567890',
277
+ expected: '123 456 789',
278
+ },
279
+ {
280
+ name: 'handle 5-digit limit with thousands separator',
281
+ input: '12345',
282
+ expected: '12 345',
283
+ options: { maxIntegerLength: 5 },
284
+ },
285
+ ];
286
+
287
+ it.each(integerLimitCases)('$name', ({ input, expected, options }) => {
288
+ expect(textFormatter(input, options)).toBe(expected);
289
+ });
290
+ });
291
+
292
+ describe('decimal length limits', () => {
293
+ type Case = {
294
+ name: string;
295
+ input: string;
296
+ expected: string;
297
+ options?: {
298
+ thousandsSeparator?: boolean;
299
+ maxDecimalLength?: number;
300
+ };
301
+ };
302
+
303
+ const decimalLimitCases: Case[] = [
304
+ {
305
+ name: 'limit to 5 decimals (123.456789012)',
306
+ input: '123.456789012',
307
+ expected: '123.45678',
308
+ options: { thousandsSeparator: false, maxDecimalLength: 5 },
309
+ },
310
+ {
311
+ name: 'limit to 3 decimals (1.123456789)',
312
+ input: '1.123456789',
313
+ expected: '1.123',
314
+ options: { thousandsSeparator: false, maxDecimalLength: 3 },
315
+ },
316
+ {
317
+ name: 'limit to 2 decimals (0.987654321)',
318
+ input: '0.987654321',
319
+ expected: '0.98',
320
+ options: { thousandsSeparator: false, maxDecimalLength: 2 },
321
+ },
322
+ {
323
+ name: 'allow exactly 9 decimals',
324
+ input: '123.123456789',
325
+ expected: '123.123456789',
326
+ options: { thousandsSeparator: false },
327
+ },
328
+ {
329
+ name: 'allow exactly maxDecimalLength digits',
330
+ input: '1.12345',
331
+ expected: '1.12345',
332
+ options: { thousandsSeparator: false, maxDecimalLength: 5 },
333
+ },
334
+ {
335
+ name: 'handle 5 decimal limit with thousands separator',
336
+ input: '123456.789012345',
337
+ expected: '123 456.78901',
338
+ options: { maxDecimalLength: 5 },
339
+ },
340
+ {
341
+ name: 'handle 9 decimals with thousands separator',
342
+ input: '1234567.123456789',
343
+ expected: '1 234 567.123456789',
344
+ },
345
+ ];
346
+
347
+ it.each(decimalLimitCases)('$name', ({ input, expected, options }) => {
348
+ expect(textFormatter(input, options)).toBe(expected);
349
+ });
350
+ });
351
+
352
+ describe('combined length limits (18 total digits)', () => {
353
+ type Case = {
354
+ name: string;
355
+ input: string;
356
+ expected: string;
357
+ options?: { thousandsSeparator?: boolean };
358
+ };
359
+
360
+ const combinedLimitCases: Case[] = [
361
+ {
362
+ name: 'allow 9 integer + 9 decimal (no separator)',
363
+ input: '123456789.987654321',
364
+ expected: '123456789.987654321',
365
+ options: { thousandsSeparator: false },
366
+ },
367
+ {
368
+ name: 'allow 9 integer + 9 decimal (with separator)',
369
+ input: '123456789.987654321',
370
+ expected: '123 456 789.987654321',
371
+ },
372
+ {
373
+ name: 'truncate excess in integer part (10 + 9)',
374
+ input: '1234567890.123456789',
375
+ expected: '123456789.123456789',
376
+ options: { thousandsSeparator: false },
377
+ },
378
+ {
379
+ name: 'truncate excess in decimal part (9 + 10)',
380
+ input: '123456789.9876543210',
381
+ expected: '123456789.987654321',
382
+ options: { thousandsSeparator: false },
383
+ },
384
+ {
385
+ name: 'truncate excess in both parts (11 + 11)',
386
+ input: '12345678901.98765432109',
387
+ expected: '123456789.987654321',
388
+ options: { thousandsSeparator: false },
389
+ },
390
+ ];
391
+
392
+ it.each(combinedLimitCases)('$name', ({ input, expected, options }) => {
393
+ expect(textFormatter(input, options)).toBe(expected);
394
+ });
395
+ });
396
+
397
+ describe('edge cases with length limits', () => {
398
+ type Case = {
399
+ name: string;
400
+ input: string;
401
+ expected: string;
402
+ options?: {
403
+ thousandsSeparator?: boolean;
404
+ maxIntegerLength?: number;
405
+ maxDecimalLength?: number;
406
+ };
407
+ };
408
+
409
+ const lengthEdgeCases: Case[] = [
410
+ {
411
+ name: 'trailing decimal point (123456789.)',
412
+ input: '123456789.',
413
+ expected: '123456789.',
414
+ options: { thousandsSeparator: false },
415
+ },
416
+ {
417
+ name: 'truncate integer with trailing decimal (1234567890.)',
418
+ input: '1234567890.',
419
+ expected: '123456789.',
420
+ options: { thousandsSeparator: false },
421
+ },
422
+ {
423
+ name: 'trailing decimal with custom limits (123.)',
424
+ input: '123.',
425
+ expected: '123.',
426
+ options: {
427
+ thousandsSeparator: false,
428
+ maxIntegerLength: 5,
429
+ maxDecimalLength: 5,
430
+ },
431
+ },
432
+ {
433
+ name: 'remove leading zeros (000123456789)',
434
+ input: '000123456789',
435
+ expected: '123456789',
436
+ options: { thousandsSeparator: false },
437
+ },
438
+ {
439
+ name: 'remove leading zeros and truncate (001234567890)',
440
+ input: '001234567890',
441
+ expected: '123456789',
442
+ options: { thousandsSeparator: false },
443
+ },
444
+ {
445
+ name: 'decimal starting with dot (.123456789)',
446
+ input: '.123456789',
447
+ expected: '0.123456789',
448
+ options: { thousandsSeparator: false },
449
+ },
450
+ {
451
+ name: 'truncate decimal starting with dot (.1234567890)',
452
+ input: '.1234567890',
453
+ expected: '0.123456789',
454
+ options: { thousandsSeparator: false },
455
+ },
456
+ {
457
+ name: 'preserve functionality (123.45)',
458
+ input: '123.45',
459
+ expected: '123.45',
460
+ options: { thousandsSeparator: false },
461
+ },
462
+ {
463
+ name: 'preserve functionality (1.2)',
464
+ input: '1.2',
465
+ expected: '1.2',
466
+ },
467
+ ];
468
+
469
+ it.each(lengthEdgeCases)('$name', ({ input, expected, options }) => {
470
+ expect(textFormatter(input, options)).toBe(expected);
471
+ });
472
+ });
473
+
474
+ describe('backwards compatibility', () => {
475
+ it('should work with undefined length parameters', () => {
476
+ // When called without length params, should use default behavior
477
+ expect(textFormatter('123456789.123456789')).toBe(
478
+ '123 456 789.123456789',
479
+ ); // Default 9 decimal limit
480
+ });
481
+ });
482
+ });
483
+
484
+ describe('complex scenarios', () => {
485
+ type Case = { name: string; input: string; expected: string };
486
+
487
+ const complexCases: Case[] = [
488
+ {
489
+ name: 'combination of all edge cases',
490
+ input: '$01,234.567890abc',
491
+ expected: '1.234567890',
492
+ },
493
+ { name: 'realistic input: 100', input: '100', expected: '100' },
494
+ { name: 'realistic input: 100.00', input: '100.00', expected: '100.00' },
495
+ { name: 'realistic input: 0100', input: '0100', expected: '100' },
496
+ { name: 'realistic input: €50.25', input: '€50.25', expected: '50.25' },
497
+ ];
498
+
499
+ it.each(complexCases)('$name', ({ input, expected }) => {
500
+ expect(textFormatter(input)).toBe(expected);
501
+ });
502
+ });
503
+ });