Un pot de crédits global (BASIC 100 / PRO 1 000 / BUSINESS 4 000) remplace les seaux par fonction côté débit. Packs one-shot S/M/L, admin sans seaux legacy, usage multi-features, hints de coût, anti-flash thème et hydratation admin. Inclut aussi le pipeline slides renforcé et la page publique polishée.
95 lines
2.9 KiB
TypeScript
95 lines
2.9 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
assertDeckHasSubstance,
|
|
isSlideBodyEmpty,
|
|
listEmptyBodySlides,
|
|
normalizeSlideDeck,
|
|
} from '@/lib/ai/services/slide-content-quality'
|
|
|
|
describe('slide deck substance gate', () => {
|
|
it('detects title-only body slides (the bug that shipped empty decks)', () => {
|
|
const emptyBullets = { type: 'bullets', title: 'Contexte marché', items: [] }
|
|
expect(isSlideBodyEmpty(emptyBullets)).toBe(true)
|
|
|
|
const withItems = {
|
|
type: 'bullets',
|
|
title: 'Les délais ont doublé',
|
|
items: ['Lead time 12→24 jours', 'Priorités instables en sprint'],
|
|
}
|
|
expect(isSlideBodyEmpty(withItems)).toBe(false)
|
|
})
|
|
|
|
it('rejects decks that only have titles', () => {
|
|
const bad = normalizeSlideDeck({
|
|
title: 'Test',
|
|
slides: [
|
|
{ type: 'title', title: 'Test' },
|
|
{ type: 'bullets', title: 'Point A', items: [] },
|
|
{ type: 'bullets', title: 'Point B' },
|
|
{ type: 'summary', title: 'Fin', items: [] },
|
|
],
|
|
})
|
|
const gate = assertDeckHasSubstance(bad)
|
|
expect(gate.ok).toBe(false)
|
|
if (!gate.ok) {
|
|
expect(gate.emptyIndexes.length).toBeGreaterThan(0)
|
|
}
|
|
})
|
|
|
|
it('accepts a minimal solid deck', () => {
|
|
const good = normalizeSlideDeck({
|
|
title: 'Accélérer la livraison',
|
|
slides: [
|
|
{
|
|
type: 'title',
|
|
title: 'Accélérer la livraison',
|
|
subtitle: 'Trois leviers en 90 jours',
|
|
},
|
|
{
|
|
type: 'bullets',
|
|
title: 'Les délais ont doublé faute de priorisation',
|
|
items: [
|
|
'Lead time passé de 12 à 24 jours',
|
|
'40% des tickets changent de priorité en sprint',
|
|
'Dépendances inter-équipes bloquent 1 livraison sur 3',
|
|
],
|
|
},
|
|
{
|
|
type: 'cards',
|
|
title: 'Trois leviers actionnables',
|
|
cards: [
|
|
{ title: 'WIP limit', description: 'Max 2 items en cours par personne' },
|
|
{ title: 'Backlog unique', description: 'Un seul ordre partagé produit/tech' },
|
|
],
|
|
},
|
|
{
|
|
type: 'summary',
|
|
title: 'Prochaines décisions',
|
|
items: [
|
|
'Valider WIP limit cette semaine',
|
|
'Fusionner les backlogs sous 10 jours',
|
|
],
|
|
},
|
|
],
|
|
})
|
|
const gate = assertDeckHasSubstance(good)
|
|
expect(gate.ok).toBe(true)
|
|
expect(listEmptyBodySlides(good.slides)).toEqual([])
|
|
})
|
|
|
|
it('flags empty charts and empty stats as empty bodies', () => {
|
|
expect(isSlideBodyEmpty({ type: 'chart', title: 'KPI', data: [] })).toBe(true)
|
|
expect(
|
|
isSlideBodyEmpty({
|
|
type: 'chart',
|
|
title: 'KPI',
|
|
data: [
|
|
{ label: 'Q1', value: 10 },
|
|
{ label: 'Q2', value: 20 },
|
|
],
|
|
}),
|
|
).toBe(false)
|
|
expect(isSlideBodyEmpty({ type: 'stats', title: 'Chiffres', stats: [] })).toBe(true)
|
|
})
|
|
})
|