feat(web): phase-aware dm_call_undeliverable toast copy (TDD)

This commit is contained in:
Jannis Braun
2026-04-23 23:21:40 +02:00
parent f6252b8ce1
commit 6a5b02b1a0
3 changed files with 104 additions and 34 deletions
@@ -0,0 +1,37 @@
import { describe, it, expect } from 'vitest';
import { buildCallUndeliverableToast } from '../../utils/callUndeliverableToast.js';
describe('buildCallUndeliverableToast', () => {
const fail = (overrides: Partial<{ reason: string; peerOrigin?: string; peerLabel?: string }> = {}) => ({
reason: 'peer_transient_failure',
peerLabel: 'nova',
...overrides,
});
it('start + terminal single failure: existing copy', () => {
expect(buildCallUndeliverableToast([fail()], true, 'start')).toMatch(/Could not reach nova/);
});
it('start + non-terminal: "some participants" copy', () => {
expect(buildCallUndeliverableToast([fail()], false, 'start')).toMatch(/Some participants could not be reached/);
});
it('accept + terminal: tear-down copy', () => {
expect(buildCallUndeliverableToast([fail()], true, 'accept'))
.toMatch(/Couldn't confirm your accept with nova/);
});
it('reject + non-terminal: info copy', () => {
expect(buildCallUndeliverableToast([fail()], false, 'reject'))
.toMatch(/Couldn't notify nova that you declined/);
});
it('end + non-terminal: info copy', () => {
expect(buildCallUndeliverableToast([fail()], false, 'end'))
.toMatch(/Couldn't notify nova that you hung up/);
});
it('legacy two-arg signature still works', () => {
expect(buildCallUndeliverableToast([fail()], true)).toMatch(/Could not reach nova/);
});
});
+3 -34
View File
@@ -116,40 +116,9 @@ function buildWsUrl(origin: string): string {
// ─── Call relay helpers ───────────────────────────────────────────────────────
function buildCallUndeliverableToast(
failures: Array<{ reason: string; peerOrigin?: string; peerLabel?: string }>,
terminal: boolean,
): string {
const primary = failures[0];
const labelFor = (f: { peerLabel?: string; peerOrigin?: string }) =>
f.peerLabel ?? f.peerOrigin?.replace(/^https?:\/\//, '') ?? 'the remote instance';
import { buildCallUndeliverableToast } from '../utils/callUndeliverableToast';
if (!terminal) {
const labels = failures.map(labelFor).join(', ');
return `Some participants could not be reached: ${labels}.`;
}
if (failures.length > 1) {
const labels = failures.map(labelFor).join(', ');
return `Could not reach ${failures.length} instances: ${labels}.`;
}
if (!primary) return 'Call could not be placed.';
const label = labelFor(primary);
switch (primary.reason) {
case 'peer_rejected':
return `Cannot reach ${label} — this instance requires manual peering approval.`;
case 'peer_awaiting_approval':
return `Waiting for ${label} admin to approve your instance. Calls will work once approved.`;
case 'peer_transient_failure':
return `Could not reach ${label}. Try again in a moment.`;
case 'livekit_unavailable':
return 'Voice is not configured on this instance.';
default:
return `Call to ${label} could not be placed.`;
}
}
export { buildCallUndeliverableToast };
// ─── Event handling ───────────────────────────────────────────────────────────
@@ -1030,7 +999,7 @@ function handleEvent(origin: string, event: ServerEvent): void {
if (disconnectFn) disconnectFn();
}
const msg = buildCallUndeliverableToast(event.failures, event.terminal);
const msg = buildCallUndeliverableToast(event.failures, event.terminal, event.phase);
addToast(msg, event.terminal ? 'warning' : 'info', 8_000);
break;
}