teqo-backup/tests/unit/contentPieceBatch.unit.spec.ts
Francisco Solla 10327a7451 feat(C236): publicar, despublicar e apagar em lote na Central de Conteúdos
A lista /campanha/comunicacao/conteudos ganha um modo de seleção opt-in
que envolve os dois corpos (tabela e card list) com um estado client
compartilhado, chaveado pela URL canônica (a seleção morre em
página/filtro e nunca cresce para além da página visível). Com seleção
ativa, a barra flutuante aplica o mesmo veredito unitário à seleção:
Publicar/Despublicar agem direto com recibo honesto; Apagar sempre
confirma no CampaignDeleteDialog reusado (novo caminho onConfirm
aditivo), avisando os links públicos só quando a seleção carrega
publicadas.

O transporte é uma rota única (POST .../conteudos/lote,
campaignJsonMutationRoute, teto de 50 ids) que itera server-side as ações
donas: cada peça mantém a própria transação, o gate fresco e a limpeza
best-effort de mídia; a falha de uma nunca reverte as demais e o recibo
separa afetadas x falhas (2 de 3 peças apagadas. 1 falhou.).

Sem migration, sem Consent novo, sem rota pública (S27) e sem mudar a
semântica do C222. Cobertura: int do lote (papéis, dedupe, mídia, evento,
tag, falha parcial, schema), unit do builder/barra/seleção e e2e HTTP.

Design tier: DEGRADED (artefato de origem sem certificação frontier) —
crítica do designer aplicada no escopo do item; entrega bloqueada para
sign-off humano antes do PR. Related #1388
2026-09-29 22:26:26 -03:00

52 lines
1.9 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
contentPieceBatchDeleteTitle,
contentPieceBatchReceiptMessage,
contentPieceSelectionLabel,
type ContentPieceBatchOutcome,
} from '@/lib/contentPieceBatch'
const outcome = (
action: ContentPieceBatchOutcome['action'],
affected: number,
failed = 0,
): ContentPieceBatchOutcome => ({
action,
affected,
failures: Array.from({ length: failed }, (_, index) => ({
contentPieceId: index + 1,
message: 'Peça não encontrada.',
})),
})
describe('contentPieceBatch (C236)', () => {
it('writes the all-success receipt in the plan literals, singular included', () => {
expect(contentPieceBatchReceiptMessage(outcome('publicar', 3))).toBe('3 peças publicadas')
expect(contentPieceBatchReceiptMessage(outcome('despublicar', 1))).toBe('1 peça despublicada')
expect(contentPieceBatchReceiptMessage(outcome('apagar', 3))).toBe('3 peças apagadas')
expect(contentPieceBatchReceiptMessage(outcome('apagar', 1))).toBe('1 peça apagada')
})
it('never calls a partial outcome a success: it separates affected from failures', () => {
expect(contentPieceBatchReceiptMessage(outcome('apagar', 2, 1))).toBe(
'2 de 3 peças apagadas. 1 falhou.',
)
expect(contentPieceBatchReceiptMessage(outcome('publicar', 1, 2))).toBe(
'1 de 3 peças publicadas. 2 falharam.',
)
expect(contentPieceBatchReceiptMessage(outcome('despublicar', 0, 1))).toBe(
'0 de 1 peça despublicada. 1 falhou.',
)
})
it('counts the selection with the TourComposerForm singular', () => {
expect(contentPieceSelectionLabel(1)).toBe('1 selecionada')
expect(contentPieceSelectionLabel(3)).toBe('3 selecionadas')
})
it('titles the mass delete and reuses the C222 singular', () => {
expect(contentPieceBatchDeleteTitle(1)).toBe('Apagar esta peça?')
expect(contentPieceBatchDeleteTitle(3)).toBe('Apagar 3 peças selecionadas?')
})
})