feat(amount) : helpers amountFormat (normalize, group, curseur)
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import {describe, expect, it} from 'vitest'
|
||||
import {normalizeAmount, formatGroupedAmount, countSignificant, caretFromSignificant} from './amountFormat'
|
||||
|
||||
describe('normalizeAmount', () => {
|
||||
it('garde le point décimal', () => {
|
||||
expect(normalizeAmount('12.5')).toBe('12.5')
|
||||
})
|
||||
it('convertit la virgule en point et nettoie', () => {
|
||||
expect(normalizeAmount('0012,345abc')).toBe('12.34')
|
||||
})
|
||||
it('normalise une décimale en tête', () => {
|
||||
expect(normalizeAmount(',5')).toBe('0.5')
|
||||
})
|
||||
it('retire les espaces', () => {
|
||||
expect(normalizeAmount('1 234 567')).toBe('1234567')
|
||||
})
|
||||
it('limite à 2 décimales', () => {
|
||||
expect(normalizeAmount('1234.567')).toBe('1234.56')
|
||||
})
|
||||
it('garde une décimale en cours de saisie', () => {
|
||||
expect(normalizeAmount('12.')).toBe('12.')
|
||||
})
|
||||
it('renvoie une chaîne vide pour une saisie non numérique', () => {
|
||||
expect(normalizeAmount('abc')).toBe('')
|
||||
})
|
||||
it('garde un zéro seul', () => {
|
||||
expect(normalizeAmount('0')).toBe('0')
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatGroupedAmount', () => {
|
||||
it('groupe la partie entière par 3 avec des espaces', () => {
|
||||
expect(formatGroupedAmount('1234567')).toBe('1 234 567')
|
||||
})
|
||||
it('utilise la virgule comme séparateur décimal', () => {
|
||||
expect(formatGroupedAmount('1234.56')).toBe('1 234,56')
|
||||
})
|
||||
it('affiche une virgule pour une décimale en cours', () => {
|
||||
expect(formatGroupedAmount('12.')).toBe('12,')
|
||||
})
|
||||
it('gère les valeurs sous 1000 sans séparateur', () => {
|
||||
expect(formatGroupedAmount('12')).toBe('12')
|
||||
})
|
||||
it('groupe avec une décimale en tête', () => {
|
||||
expect(formatGroupedAmount('0.5')).toBe('0,5')
|
||||
})
|
||||
it('renvoie une chaîne vide pour une chaîne vide', () => {
|
||||
expect(formatGroupedAmount('')).toBe('')
|
||||
})
|
||||
it('garde un zéro seul', () => {
|
||||
expect(formatGroupedAmount('0')).toBe('0')
|
||||
})
|
||||
})
|
||||
|
||||
describe('countSignificant', () => {
|
||||
it('compte les caractères hors espaces à gauche du curseur', () => {
|
||||
expect(countSignificant('1 234', 5)).toBe(4)
|
||||
})
|
||||
it('ignore un espace juste avant le curseur', () => {
|
||||
expect(countSignificant('1 234', 2)).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('caretFromSignificant', () => {
|
||||
it('place le curseur après le n-ième caractère significatif', () => {
|
||||
expect(caretFromSignificant('1 234 567', 4)).toBe(5)
|
||||
})
|
||||
it('place le curseur en fin si on dépasse', () => {
|
||||
expect(caretFromSignificant('1 234', 10)).toBe(5)
|
||||
})
|
||||
it('place le curseur au début pour 0 caractère significatif', () => {
|
||||
expect(caretFromSignificant('1 234', 0)).toBe(0)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
// Parse : texte saisi (espaces, virgule, caractères parasites) → chaîne numérique propre.
|
||||
export const normalizeAmount = (value: string): string => {
|
||||
const sanitizedValue = value
|
||||
.replace(/\s+/g, '')
|
||||
.replace(/,/g, '.')
|
||||
.replace(/[^\d.]/g, '')
|
||||
const [integerPartRaw = '', ...decimalParts] = sanitizedValue.split('.')
|
||||
const integerPart = integerPartRaw.replace(/^0+(?=\d)/, '')
|
||||
const decimalPart = decimalParts.join('').slice(0, 2)
|
||||
|
||||
if (sanitizedValue.includes('.')) {
|
||||
return `${integerPart || '0'}.${decimalPart}`
|
||||
}
|
||||
|
||||
return integerPart
|
||||
}
|
||||
|
||||
// Format : modèle propre (point décimal) → affichage groupé FR (espaces + virgule).
|
||||
export const formatGroupedAmount = (model: string): string => {
|
||||
if (model === '') return ''
|
||||
const hasDot = model.includes('.')
|
||||
const [integerPart = '', decimalPart = ''] = model.split('.')
|
||||
const groupedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ' ')
|
||||
return hasDot ? `${groupedInteger},${decimalPart}` : groupedInteger
|
||||
}
|
||||
|
||||
// Nombre de caractères significatifs (hors espaces de groupement) à gauche d'une position.
|
||||
export const countSignificant = (str: string, upTo: number): number =>
|
||||
str.slice(0, upTo).replace(/ /g, '').length
|
||||
|
||||
// Position de curseur après le n-ième caractère significatif dans la chaîne affichée.
|
||||
export const caretFromSignificant = (display: string, sig: number): number => {
|
||||
if (sig <= 0) return 0
|
||||
let seen = 0
|
||||
for (let i = 0; i < display.length; i++) {
|
||||
if (display[i] !== ' ') seen++
|
||||
if (seen >= sig) return i + 1
|
||||
}
|
||||
return display.length
|
||||
}
|
||||
Reference in New Issue
Block a user