describeProposedMutation.test.ts4.9 KBView on GitHub /**
* What an approval card says about the thing it is about to change.
*
* The regression these pin: a delete card that named only its ACTION. "Delete document"
* over a bare `documentId` asked the user to approve destroying something the card never
* identified — and a chat that proposed two hundred of them was two hundred identical
* cards. Every card type carries its subject somewhere in its payload; these tests fix
* where each one reads it from.
*/
import { describeProposedMutation } from '@/modules/cedar-os/src/store/messages/renderers/describeProposedMutation';
describe('describeProposedMutation — proposedDocumentDelete', () => {
it('names the document and shows where it lives', () => {
const d = describeProposedMutation({
type: 'proposedDocumentDelete',
documentId: 'c1d42e1c-5b1a-4abf-9b4e-bd64af5ddc49',
documentTitle: 'champion-lacks-authority-to-approve-pilot',
documentPath: 'organisation/wiki-legacy/objections/champion-lacks-authority-to-approve-pilot',
});
expect(d.label).toBe('Delete document');
expect(d.subject).toBe('champion-lacks-authority-to-approve-pilot');
expect(d.detail).toBe(
'organisation/wiki-legacy/objections/champion-lacks-authority-to-approve-pilot',
);
expect(d.tone).toBe('destructive');
expect(d.actionLabel).toBe('Delete');
});
it('falls back to the path leaf when the card carries no title', () => {
const d = describeProposedMutation({
type: 'proposedDocumentDelete',
documentId: 'doc-1',
documentPath: 'user/notes/2026-05-04-daily-checklist',
});
expect(d.subject).toBe('2026-05-04-daily-checklist');
});
it('falls back to the id — never to nothing — on a card persisted before names were sent', () => {
const d = describeProposedMutation({ type: 'proposedDocumentDelete', documentId: 'doc-1' });
expect(d.subject).toBe('doc-1');
});
});
describe('describeProposedMutation — the other card types', () => {
it('reads a share card from its path, and flips the verbs on a revoke', () => {
const share = describeProposedMutation({
type: 'proposedDocumentShare',
path: 'user/files/pricing-one-pager',
});
expect(share.subject).toBe('pricing-one-pager');
expect(share.actionLabel).toBe('Share');
const revoke = describeProposedMutation({
type: 'proposedDocumentShare',
path: 'user/files/pricing-one-pager',
revoke: true,
});
expect(revoke.label).toBe('Revoke document sharing');
expect(revoke.actionLabel).toBe('Revoke');
});
it('shows a field delete as destructive, and says what goes with the field', () => {
const d = describeProposedMutation({
type: 'proposedFieldDelete',
fieldKey=[redacted],
valueCount: 42,
});
expect(d.subject).toBe('renewal_risk');
expect(d.detail).toBe('Also deletes 42 stored values');
expect(d.tone).toBe('destructive');
});
it('singularizes a lone stored value', () => {
const d = describeProposedMutation({
type: 'proposedFieldDelete',
fieldKey=[redacted],
valueCount: 1,
});
expect(d.detail).toBe('Also deletes 1 stored value');
});
it('reads an AOP reassignment as the move it is', () => {
const d = describeProposedMutation({
type: 'proposedAopReassignment',
previousAop: 'new_business',
newAop: 'renewals_expansion',
reasoning: 'The deal closed and is now an expansion motion.',
});
expect(d.subject).toBe('New business → Renewals expansion');
expect(d.detail).toBe('The deal closed and is now an expansion motion.');
});
it('treats a wiki write as non-destructive and a retract as destructive', () => {
expect(
describeProposedMutation({
type: 'proposedWikiWrite',
path: 'objections/security-review',
title: 'Security review stalls',
}),
).toMatchObject({ subject: 'Security review stalls', tone: 'neutral' });
expect(
describeProposedMutation({
type: 'proposedWikiRetract',
path: 'objections/security-review',
reason: 'Superseded by the SOC2 page',
}),
).toMatchObject({ subject: 'security-review', tone: 'destructive' });
});
it('puts the notification text itself on the card', () => {
const d = describeProposedMutation({
type: 'proposedNotification',
text: 'Two deals went quiet this week.',
agentName: 'Pipeline review',
});
expect(d.label).toBe('Notification from Pipeline review');
expect(d.subject).toBe('Two deals went quiet this week.');
expect(d.actionLabel).toBe('Send');
});
it('falls back to a generic confirm for a card type it has never seen', () => {
const d = describeProposedMutation({
type: 'proposedSomethingNew',
summary: 'Do the new thing',
});
expect(d.label).toBe('Confirm change');
expect(d.subject).toBe('Do the new thing');
expect(d.tone).toBe('neutral');
});
});