1import { describe, expect, it } from "vitest";2import {3 describeSyncOutcome,4 isSyncFailure,5 isSyncWarning,6 syncIndicator,7 syncIndicatorLabel,8 type SyncOutcome,9} from "./sync-outcome";1011describe("syncIndicator", () => {12 it("pulses green only while a sync is actually running", () => {13 expect(syncIndicator({ status: "syncing" }, 1)).toBe("busy");14 15 expect(syncIndicator({ status: "syncing", outcome: "relay_unreachable" }, 1)).toBe("busy");16 });1718 it("is solid green for a completed sync, with or without changes", () => {19 expect(syncIndicator({ status: "idle", outcome: "completed_changes" }, 1)).toBe("ok");20 expect(syncIndicator({ status: "idle", outcome: "completed_no_changes" }, 1)).toBe("ok");21 });2223 it("never shows green for a paired-but-unsyncable device", () => {24 25 26 expect(syncIndicator({ status: "error", outcome: "no_eligible_host" }, 2)).toBe("error");27 expect(syncIndicator({ status: "error", outcome: "runner_unavailable" }, 2)).toBe("error");28 });2930 it("shows yellow for network-class misses — relay/handshake/timeout are warn, not error", () => {31 32 33 34 expect(syncIndicator({ status: "idle", outcome: "relay_unreachable" }, 1)).toBe("warn");35 expect(syncIndicator({ status: "idle", outcome: "handshake_failed" }, 1)).toBe("warn");36 expect(syncIndicator({ status: "idle", outcome: "sync_timeout" }, 1)).toBe("warn");37 });3839 it("keeps red on genuine configuration / identity problems that the alarm cannot fix", () => {40 expect(syncIndicator({ status: "error", outcome: "vault_id_mismatch" }, 1)).toBe("error");41 expect(syncIndicator({ status: "error", outcome: "private_relay_unset" }, 1)).toBe("error");42 expect(syncIndicator({ status: "error", outcome: "no_eligible_host" }, 1)).toBe("error");43 expect(syncIndicator({ status: "error", outcome: "runner_unavailable" }, 1)).toBe("error");44 expect(syncIndicator({ status: "error", outcome: "run_failed" }, 1)).toBe("error");45 });4647 it("treats vault_locked as warn (yellow) — the user hasn't unlocked yet, not a misconfig", () => {48 49 50 51 52 expect(syncIndicator({ status: "error", outcome: "vault_locked" }, 1)).toBe("warn");53 expect(syncIndicator({ status: "idle", outcome: "vault_locked" }, 1)).toBe("warn");54 });5556 it("returns to green after a successful dial even when the prior attempt was warn", () => {57 58 const prior: { status: "idle"; outcome: "completed_changes" } = {59 status: "idle",60 outcome: "completed_changes",61 };62 expect(syncIndicator(prior, 1)).toBe("ok");63 });6465 it("stays unlit when there is nothing paired and nothing attempted", () => {66 expect(syncIndicator({}, 0)).toBe("idle");67 expect(syncIndicator({ status: "idle", outcome: "no_paired_devices" }, 0)).toBe("idle");68 });6970 it("reads a healthy pair as green before the first attempt of this session", () => {71 expect(syncIndicator({ status: "idle" }, 1)).toBe("ok");72 });7374 it("resolves every outcome to exactly one indicator, none falling through to ok", () => {75 const outcomes: SyncOutcome[] = [76 "completed_changes", "completed_no_changes", "no_paired_devices",77 "no_eligible_host", "runner_unavailable", "sync_disabled",78 "private_relay_unset", "vault_locked", "relay_unreachable",79 "handshake_failed", "vault_id_mismatch", "sync_timeout", "run_failed",80 ];81 for (const outcome of outcomes) {82 const light = syncIndicator({ status: "idle", outcome }, 1);83 expect(isSyncFailure(outcome) ? light : "not-error").not.toBe("ok");84 if (isSyncFailure(outcome)) expect(light).toBe("error");85 if (isSyncWarning(outcome)) expect(light).toBe("warn");86 }87 });88});8990describe("describeSyncOutcome", () => {91 it("names what moved on a successful sync", () => {92 expect(93 describeSyncOutcome({ outcome: "completed_changes", entriesSent: 2, entriesReceived: 1 }),94 ).toBe("Synced — 2 sent, 1 received");95 });9697 it("distinguishes a successful no-op from a failure in plain English", () => {98 expect(describeSyncOutcome({ outcome: "completed_no_changes" }))99 .toBe("Up to date — nothing to send or receive");100 expect(describeSyncOutcome({ outcome: "no_eligible_host" }))101 .not.toContain("Up to date");102 });103104 it("keeps the raw diagnostic detail visible after the plain-English reason", () => {105 106 const line = describeSyncOutcome({107 outcome: "no_eligible_host",108 detail: "Pixel [type=phone, trust=direct]",109 });110 expect(line).toContain("no device here can accept a sync");111 expect(line).toContain("Pixel [type=phone, trust=direct]");112 });113114 it("says nothing has happened yet rather than implying success", () => {115 expect(describeSyncOutcome({})).toBe("Not synced yet");116 });117});118119describe("syncIndicatorLabel", () => {120 it("states the colour's claim in words", () => {121 expect(syncIndicatorLabel("error", { outcome: "vault_id_mismatch" }))122 .toBe("Sync problem — That device holds a different vault");123 });124125 it("states the warn colour's claim in words — the alarm retries", () => {126 expect(syncIndicatorLabel("warn", { outcome: "relay_unreachable" }))127 .toBe("Last sync attempt missed — will retry — Couldn't reach the relay");128 });129});