+ Aucune variable : ce modèle produit toujours le même + texte. +
+ )} + ++ L’affectation d’une base légale est une qualification juridique. Ce + tableau porte la lecture usuelle d’un traitement de paie et de planning ; + il doit être confirmé par le responsable de traitement avant d’être + opposé à qui que ce soit. +
+ + {undeclared > 0 ? ( ++ {undeclared} catégorie{undeclared > 1 ? 's' : ''} sans durée de + conservation déclarée. Le plan interdit d’appliquer une durée par + défaut : déclarez-la dans{' '} + + Durées de conservation + + . +
+ ) : null} + +| + {heading} + | + ))} +||||
|---|---|---|---|---|
|
+ {entry.category}
+ {entry.sensitive ? (
+ |
+ + {entry.purpose} + {entry.notes ? ( + + {entry.notes} + + ) : null} + | ++ {LEGAL_BASIS_LABELS[entry.basis]} + | ++ {entry.recipients.join(', ')} + | +
+ {duration === 'Non déclarée' ? (
+ |
+
+ L’accès, la rectification et l’effacement s’exercent sur le dossier + du salarié. L’effacement ne franchit pas les durées de conservation + légales : une pièce sous obligation de conservation ne s’efface pas + à la demande, et le refus doit être motivé. +
++ La portabilité passe par le paquet de départ : export filtré des + données du salarié, accompagné de son manifeste et de son empreinte. +
++ Toute lecture d’une pièce de santé est journalisée et consultable + dans le{' '} + + journal d’activité + + . +
+{{salarie.nom}} — {{salarie.nom}} {{contrat.debut}}
'; + expect(referencedFields(body)).toEqual(['salarie.nom', 'contrat.debut']); + }); + + it('tolère les espaces dans les accolades', () => { + expect(referencedFields('{{ salarie.nom }}')).toEqual(['salarie.nom']); + }); +}); + +describe('unknownFields', () => { + it('signale une variable que l’application ne sait pas résoudre', () => { + // Le cas qui compte : la faute de frappe. Sans ce contrôle, le modèle + // s'enregistre et échoue au moment de générer la pièce, devant le salarié. + expect(unknownFields('{{salarie.salire}}')).toEqual(['salarie.salire']); + }); + + it('accepte toutes les variables du catalogue', () => { + const body = TEMPLATE_FIELD_KEYS.map((key) => `{{${key}}}`).join(' '); + expect(unknownFields(body)).toEqual([]); + }); +}); + +describe('renderTemplate', () => { + it('remplace les variables par leur valeur', () => { + const out = renderTemplate('{{salarie.nom}}
', { + 'salarie.nom': 'Ferrand', + }); + expect(out).toBe('Ferrand
'); + }); + + it('échappe la valeur, jamais le gabarit', () => { + // Le corps est rédigé par un administrateur et reste de l'HTML ; la valeur + // vient du dossier et pourrait porter un chevron. + const out = renderTemplate('{{salarie.nom}}
', { + 'salarie.nom': '', + }); + expect(out).toBe( + '<script>alert(1)</script>
', + ); + }); + + it('refuse de rendre une variable sans valeur', () => { + // Une attestation trouée est un document faux, pas un document incomplet : + // le blanc se remarque une fois sur deux, et la pièce part signée. + expect(() => + renderTemplate('{{salarie.nom}} {{contrat.debut}}', { + 'salarie.nom': 'Ferrand', + }), + ).toThrow(MissingTemplateValue); + }); + + it('traite la chaîne vide comme une valeur manquante', () => { + expect(() => + renderTemplate('{{salarie.nom}}', { 'salarie.nom': '' }), + ).toThrow(MissingTemplateValue); + }); + + it('nomme les variables manquantes dans l’erreur', () => { + try { + renderTemplate('{{salarie.nom}} {{contrat.debut}}', {}); + expect.unreachable('le rendu aurait dû échouer'); + } catch (error) { + expect(error).toBeInstanceOf(MissingTemplateValue); + expect((error as MissingTemplateValue).keys).toEqual([ + 'salarie.nom', + 'contrat.debut', + ]); + } + }); +});