5e15c1f69f
Auto Tag Develop / tag (push) Successful in 11s
Lot de retours métier **ERP-193** (« Fix tous les retours starseed »), transverse aux 4 répertoires (clients, fournisseurs, prestataires, transporteurs).
## Contenu
- **Pagination** : défaut à 25 items/page sur les 4 répertoires.
- **Libellé** : colonne « Dernière activité » → « Dernière modification ».
- **Consultation** : masquage des onglets vides (coquilles « à venir » + onglets de données sans donnée).
- **Chiffre d'affaires** : plafonné à 999 999 999 999,99 (clamp front + `Assert\LessThanOrEqual` back).
- **Date de création** : interdiction des dates futures (`:max` MalioDate + `Assert\LessThanOrEqual('today')` back).
- **Caractères spéciaux** : blocage des caractères parasites (`²³§~#|…`) dans les champs texte via une allow-list par profil (nom de personne / texte libre / adresse / code alphanumérique) — filtrage front à la frappe + `Assert\Regex` back autoritaire. Email/IBAN/BIC/TVA conservent leurs validateurs de format.
- **UI** : champs en consultation et onglets validés grisés (`readonly` → `disabled`).
- **UI** : boutons « Archiver » en rouge (variant `danger`).
## Tests
- Back : nouveaux tests RG (plafond CA, dates futures, caractères spéciaux) + garde-fou contraintes — suite complète verte (813 tests).
- Front : nouveaux tests unitaires (sanitizers, helpers date/montant) — 615 tests verts, eslint clean.
---------
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Reviewed-on: #139
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
128 lines
5.2 KiB
TypeScript
128 lines
5.2 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest'
|
|
import { removeCollectionRow, isRowRemovable, type DeletableRow } from '../collectionRow'
|
|
|
|
/**
|
|
* Tests de `removeCollectionRow` — suppression d'une ligne de collection
|
|
* (contact / adresse / RIB) avec DELETE immediat de la sous-ressource existante
|
|
* (ERP-172). Coeur de logique mutualise par les 3 modules (Client / Fournisseur /
|
|
* Prestataire) : un seul comportement teste ici couvre les 9 cas (3 modules x 3
|
|
* blocs).
|
|
*/
|
|
interface Row extends DeletableRow {
|
|
label?: string
|
|
}
|
|
|
|
function makeEmpty(): Row {
|
|
return { id: null, label: '' }
|
|
}
|
|
|
|
describe('removeCollectionRow', () => {
|
|
it('emet un DELETE sur la sous-ressource quand le bloc est existant (id non null)', async () => {
|
|
const rows: Row[] = [{ id: 10, label: 'A' }, { id: 11, label: 'B' }]
|
|
const errors: Record<string, string>[] = [{}, {}]
|
|
const deleteRow = vi.fn().mockResolvedValue(undefined)
|
|
const onError = vi.fn()
|
|
const onSuccess = vi.fn()
|
|
|
|
const removed = await removeCollectionRow({
|
|
rows, errors, index: 0,
|
|
endpoint: '/client_contacts',
|
|
deleteRow, makeEmpty, onError, onSuccess,
|
|
})
|
|
|
|
expect(deleteRow).toHaveBeenCalledOnce()
|
|
expect(deleteRow).toHaveBeenCalledWith('/client_contacts/10')
|
|
expect(removed).toBe(true)
|
|
expect(rows).toEqual([{ id: 11, label: 'B' }])
|
|
expect(errors).toHaveLength(1)
|
|
expect(onError).not.toHaveBeenCalled()
|
|
// Toast de succes uniquement sur suppression serveur confirmee.
|
|
expect(onSuccess).toHaveBeenCalledOnce()
|
|
})
|
|
|
|
it('ne fait AUCUN appel reseau pour un bloc jamais persiste (id null) — retrait local', async () => {
|
|
const rows: Row[] = [{ id: 10, label: 'A' }, { id: null, label: 'brouillon' }]
|
|
const errors: Record<string, string>[] = [{}, {}]
|
|
const deleteRow = vi.fn().mockResolvedValue(undefined)
|
|
const onError = vi.fn()
|
|
const onSuccess = vi.fn()
|
|
|
|
const removed = await removeCollectionRow({
|
|
rows, errors, index: 1,
|
|
endpoint: '/client_contacts',
|
|
deleteRow, makeEmpty, onError, onSuccess,
|
|
})
|
|
|
|
expect(deleteRow).not.toHaveBeenCalled()
|
|
expect(removed).toBe(true)
|
|
expect(rows).toEqual([{ id: 10, label: 'A' }])
|
|
// Retrait d'un simple brouillon local : pas de toast « supprime ».
|
|
expect(onSuccess).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('conserve le bloc et remonte l\'erreur si le DELETE serveur echoue (ex. 409 dernier RIB LCR)', async () => {
|
|
const rows: Row[] = [{ id: 10, label: 'A' }, { id: 11, label: 'B' }]
|
|
const errors: Record<string, string>[] = [{}, {}]
|
|
const error = { response: { status: 409 } }
|
|
const deleteRow = vi.fn().mockRejectedValue(error)
|
|
const onError = vi.fn()
|
|
|
|
const removed = await removeCollectionRow({
|
|
rows, errors, index: 0,
|
|
endpoint: '/client_ribs',
|
|
deleteRow, makeEmpty, onError,
|
|
})
|
|
|
|
expect(removed).toBe(false)
|
|
expect(onError).toHaveBeenCalledWith(error)
|
|
// Bloc NON retire : la suppression n'a pas ete confirmee par le serveur.
|
|
expect(rows).toEqual([{ id: 10, label: 'A' }, { id: 11, label: 'B' }])
|
|
expect(errors).toHaveLength(2)
|
|
})
|
|
|
|
it('garde au moins un bloc visible apres retrait du dernier (amorce vide)', async () => {
|
|
const rows: Row[] = [{ id: 10, label: 'A' }]
|
|
const errors: Record<string, string>[] = [{}]
|
|
const deleteRow = vi.fn().mockResolvedValue(undefined)
|
|
|
|
await removeCollectionRow({
|
|
rows, errors, index: 0,
|
|
endpoint: '/client_contacts',
|
|
deleteRow, makeEmpty, onError: vi.fn(),
|
|
})
|
|
|
|
expect(rows).toEqual([{ id: null, label: '' }])
|
|
})
|
|
})
|
|
|
|
/**
|
|
* Tests de `isRowRemovable` — la poubelle d'un bloc n'apparait que s'il reste un
|
|
* AUTRE bloc deja enregistre (id en base). Empeche de supprimer un bloc tant que
|
|
* rien n'est sauvegarde, et de supprimer son dernier bloc enregistre (ERP-172).
|
|
*/
|
|
describe('isRowRemovable', () => {
|
|
it('faux quand aucun autre bloc n\'est enregistre (que des brouillons)', () => {
|
|
const rows: Row[] = [{ id: null, label: 'brouillon 1' }, { id: null, label: 'brouillon 2' }]
|
|
expect(isRowRemovable(rows, 0)).toBe(false)
|
|
expect(isRowRemovable(rows, 1)).toBe(false)
|
|
})
|
|
|
|
it('faux pour le seul bloc enregistre (un brouillon a cote ne compte pas)', () => {
|
|
const rows: Row[] = [{ id: 10, label: 'enregistre' }, { id: null, label: 'brouillon' }]
|
|
// Le bloc enregistre ne peut pas etre supprime : aucun AUTRE bloc enregistre.
|
|
expect(isRowRemovable(rows, 0)).toBe(false)
|
|
// Le brouillon peut etre jete : il reste le bloc enregistre id=10.
|
|
expect(isRowRemovable(rows, 1)).toBe(true)
|
|
})
|
|
|
|
it('vrai pour chaque bloc des qu\'au moins deux sont enregistres', () => {
|
|
const rows: Row[] = [{ id: 10, label: 'A' }, { id: 11, label: 'B' }]
|
|
expect(isRowRemovable(rows, 0)).toBe(true)
|
|
expect(isRowRemovable(rows, 1)).toBe(true)
|
|
})
|
|
|
|
it('faux pour un unique bloc', () => {
|
|
expect(isRowRemovable([{ id: 10, label: 'A' }], 0)).toBe(false)
|
|
})
|
|
})
|