import { describe, expect, it } from 'vitest' import { CODE_LIKE_ATTRIBUTE_WORDS, CODE_PATTERN_CANONICAL_FORM, CODE_PATTERN_LINE_RE, CODE_SAISI_LINE_RE, ENT_BLOCK_RE, codeLikeWordOf, findCodePatternNearMisses, hasSequence, isCodeLikeAttributeName, parseCodePatternLine, parseCodeSaisiValue, referencedFields, tokenNames, unknownTokens, validateFormat, } from '../code-pattern-grammar.js' // lib/code-pattern-grammar — the socle's CLOSED grammar (promoted from // scaffold-coded-entity) + the `**Code pattern**` line parser shared by both // scaffolder halves, DEV-API-022 and derive-code-specs. describe('grammar primitives', () => { it('recognises engine + derived tokens, flags invented ones', () => { expect(tokenNames('AFF-{YY}-{SEQ:4}')).toEqual(['YY', 'SEQ']) expect(hasSequence('AFF-{YY}-{SEQ:4}')).toBe(true) expect(unknownTokens('OPP-{YY}-{NNNN}')).toEqual(['{NNNN}']) expect(referencedFields('X-{ABBR:ClientName:3}-{SLUG:Name}-{SEQ:4}')).toEqual(['ClientName', 'Name']) }) it('validateFormat mirrors the engine: unknown token / no {SEQ} / reset misalignment are errors', () => { expect(validateFormat('OPP-{YY}-{SEQ:4}', 'Yearly')).toEqual([]) expect(validateFormat('OPP-{NNNN}', 'None').join(' ')).toContain('Unrecognised') expect(validateFormat('OPP-FIXED', 'None').join(' ')).toContain('no {SEQ}') expect(validateFormat('OPP-{SEQ:4}', 'Yearly').join(' ')).toContain('Reset=Yearly') }) }) describe('the **Code pattern** line — shared regexes', () => { it('ENT_BLOCK_RE + CODE_PATTERN_LINE_RE read the doc-templates skeleton shape', () => { expect(ENT_BLOCK_RE.exec('### ENT-001 — Opportunity (agrégat racine)')?.[1]).toBe('Opportunity') const line = '- **Code pattern** : `OPP-{YY}-{SEQ:4}` — scope tenant, reset annuel, gapless (voir BR-002).' expect(CODE_PATTERN_LINE_RE.exec(line)?.[1]).toContain('`OPP-{YY}-{SEQ:4}`') }) }) describe('parseCodePatternLine — FR/EN tolerant facets, null = not authored', () => { it('parses the canonical FR line (doc-templates shape)', () => { const p = parseCodePatternLine('`OPP-{YY}-{SEQ:4}` — scope tenant, reset annuel, gapless (voir BR-002). Alloué par le socle.') expect(p).toEqual({ format: 'OPP-{YY}-{SEQ:4}', scope: 'Tenant', reset: 'Yearly', gapless: true, label: null, supplied: null, }) }) it('parses EN facets and the other reset periods', () => { expect(parseCodePatternLine('`INV-{YYYY}-{MM}-{SEQ:5}` — global scope, monthly reset, gapless').scope).toBe('Global') expect(parseCodePatternLine('`X-{SEQ:3}` — reset mensuel').reset).toBe('Monthly') expect(parseCodePatternLine('`X-{YY}{MM}{DD}-{SEQ:3}` — reset quotidien').reset).toBe('Daily') expect(parseCodePatternLine('`X-{SEQ:3}` — daily reset').reset).toBe('Daily') expect(parseCodePatternLine('`X-{SEQ:3}` — sans reset').reset).toBe('None') expect(parseCodePatternLine('`X-{SEQ:3}` — reset: none').reset).toBe('None') }) it('negatives first on gapless — "non gapless"/HiLo/avec trous → false', () => { expect(parseCodePatternLine('`X-{SEQ:3}` — non gapless (HiLo)').gapless).toBe(false) expect(parseCodePatternLine('`X-{SEQ:3}` — avec trous tolérés').gapless).toBe(false) expect(parseCodePatternLine('`X-{SEQ:3}` — sans trou').gapless).toBe(true) }) it('an unauthored facet is null, NEVER a default — the audit legs skip the comparison on null', () => { const p = parseCodePatternLine('`OPP-{YY}-{SEQ:4}`') expect(p.format).toBe('OPP-{YY}-{SEQ:4}') expect(p.scope).toBeNull() expect(p.reset).toBeNull() expect(p.gapless).toBeNull() }) it('the {TENANT} token inside the mask never reads as the tenant scope facet', () => { const p = parseCodePatternLine('`{TENANT}-{SEQ:4}` — reset annuel') expect(p.scope).toBeNull() expect(p.format).toBe('{TENANT}-{SEQ:4}') }) it('a line without a backticked mask yields format null (free prose is not a mask)', () => { expect(parseCodePatternLine('scope tenant, reset annuel').format).toBeNull() }) }) describe('label + supplied facets — null = not authored, quote-delimited label', () => { it('parses the FR label with French or straight quotes', () => { expect(parseCodePatternLine('`AFF-{YY}-{SEQ:4}` — scope tenant, libellé « Référence »').label) .toEqual({ fr: 'Référence', en: null }) expect(parseCodePatternLine('`AFF-{SEQ:4}` — libelle "Reference dossier"').label) .toEqual({ fr: 'Reference dossier', en: null }) }) it('parses the EN label, and FR+EN coexisting on one line', () => { expect(parseCodePatternLine('`AFF-{SEQ:4}` — label "Reference"').label) .toEqual({ fr: null, en: 'Reference' }) const both = parseCodePatternLine('`AFF-{SEQ:4}` — libellé « Référence », label "Reference"').label expect(both).toEqual({ fr: 'Référence', en: 'Reference' }) }) it('supplied: FR/EN positives, negatives FIRST, absent = null', () => { expect(parseCodePatternLine('`A-{SEQ:3}` — surchargeable à la création').supplied).toBe(true) expect(parseCodePatternLine('`A-{SEQ:3}` — supplied on create').supplied).toBe(true) expect(parseCodePatternLine('`A-{SEQ:3}` — non surchargeable').supplied).toBe(false) expect(parseCodePatternLine('`A-{SEQ:3}` — never supplied').supplied).toBe(false) expect(parseCodePatternLine('`A-{SEQ:3}` — scope tenant, gapless').supplied).toBeNull() expect(parseCodePatternLine('`A-{SEQ:3}` — scope tenant').label).toBeNull() }) it('a « libellé » spelled inside the backticked mask never reads as the facet', () => { expect(parseCodePatternLine('`libellé-{SEQ:4}` — scope tenant').label).toBeNull() }) }) describe('findCodePatternNearMisses — the green-by-vacuity killer', () => { const doc = (body: string): string => `### ENT-001 — Catalogue ${body} ` it('flags the table-cell declaration (the incident shape) with its entity', () => { const misses = findCodePatternNearMisses(doc('| Code pattern | `CAT-{SEQ:4}` — scope tenant |')) expect(misses).toHaveLength(1) expect(misses[0]).toMatchObject({ entity: 'Catalogue', shape: 'table-cell', line: 3 }) }) it('classifies wrong-case and unbolded bullets', () => { expect(findCodePatternNearMisses(doc('- **Code Pattern** : `X-{SEQ:4}`'))[0]?.shape).toBe('wrong-case') expect(findCodePatternNearMisses(doc('- Code pattern : `X-{SEQ:4}`'))[0]?.shape).toBe('unbolded-bullet') expect(findCodePatternNearMisses(doc('* **Code pattern** : `X-{SEQ:4}`'))[0]?.shape).toBe('wrong-case') }) it('flags declarative prose (colon-value or orphan mask), ignores bare mentions', () => { expect(findCodePatternNearMisses(doc('Le pattern de code : `CAT-{SEQ:4}`'))[0]?.shape).toBe('prose-declaration') expect(findCodePatternNearMisses(doc('Une note qui parle du Code pattern en passant.'))).toHaveLength(0) }) it('never flags the canonical line, a stated absence, or an unrelated doc', () => { expect(findCodePatternNearMisses(doc('- **Code pattern** : `CAT-{SEQ:4}` — scope tenant, gapless'))).toHaveLength(0) expect(findCodePatternNearMisses(doc('Pas de Code pattern : le test de décision ne passe pas.'))).toHaveLength(0) expect(findCodePatternNearMisses(doc('No code pattern (typed referential key).'))).toHaveLength(0) expect(findCodePatternNearMisses(`# Rien à voir Du texte. `)).toHaveLength(0) }) it('the shared canonical-form remedy names the bullet and the table-cell ban', () => { expect(CODE_PATTERN_CANONICAL_FORM).toContain('**Code pattern**') expect(CODE_PATTERN_CANONICAL_FORM).toContain('NEVER a table cell') }) }) describe('code-like attribute lexicon — « Référence » IS a code', () => { it('matches whole words across FR/EN and compound names, accent-folded', () => { expect(codeLikeWordOf('Référence')).toBe('reference') expect(codeLikeWordOf('NuméroClient')).toBe('numero') expect(codeLikeWordOf('InvoiceNumber')).toBe('number') expect(codeLikeWordOf('Matricule')).toBe('matricule') expect(isCodeLikeAttributeName('numero_dossier')).toBe(true) }) it('never substring-matches, never flags FK-shaped names or plain code', () => { expect(codeLikeWordOf('Preference')).toBeNull() expect(codeLikeWordOf('ReferenceId')).toBeNull() expect(codeLikeWordOf('Code')).toBeNull() expect(codeLikeWordOf('CodeClient')).toBeNull() }) it('the lexicon is the drift-locked v1 set', () => { expect([...CODE_LIKE_ATTRIBUTE_WORDS]).toEqual(['reference', 'ref', 'numero', 'number', 'num', 'matricule']) }) }) describe('the **Code saisi** marking — TYPED species said out loud', () => { it('CODE_SAISI_LINE_RE reads FR and the EN alias', () => { expect(CODE_SAISI_LINE_RE.exec('- **Code saisi** : Reference — clé tapée')?.[1]).toContain('Reference') expect(CODE_SAISI_LINE_RE.exec('- **Typed code** : Reference')?.[1]).toBe('Reference') }) it('parseCodeSaisiValue names the attributes, prose after the dash ignored', () => { expect(parseCodeSaisiValue('Reference — clé référentielle tapée par l’utilisateur')).toEqual(['Reference']) expect(parseCodeSaisiValue('Reference, NumeroInterne — deux clés')).toEqual(['Reference', 'NumeroInterne']) }) }) describe('near-miss mentions — synonym-worded pattern bullets (« référence = code »)', () => { const doc = (body: string): string => `### ENT-001 — Dossier ${body} ` it('a `**Référence pattern**` / `**Matricule pattern**` bullet is a near-miss (wrong-case shape)', () => { expect(findCodePatternNearMisses(doc('- **Référence pattern** : `REF-{SEQ:4}` — scope tenant'))[0]) .toMatchObject({ entity: 'Dossier', shape: 'wrong-case' }) expect(findCodePatternNearMisses(doc('- **Matricule pattern** : `MAT-{SEQ:5}`'))[0]?.shape).toBe('wrong-case') }) it('« pattern de numérotation » in a declarative shape is caught; its stated absence is not', () => { expect(findCodePatternNearMisses(doc('Le pattern de numérotation : `DOS-{SEQ:4}`'))[0]?.shape) .toBe('prose-declaration') expect(findCodePatternNearMisses(doc('Pas de pattern de numérotation : clé naturelle suffisante.'))) .toHaveLength(0) }) })