1import { describe, expect, it } from "vitest";2import {3 describeSyncOutcome,4 isSyncFailure,5 syncIndicator,6 syncIndicatorLabel,7 type SyncOutcome,8} from "./sync-outcome";910describe("syncIndicator", () => {11 it("pulses green only while a sync is actually running", () => {12 expect(syncIndicator({ status: "syncing" }, 1)).toBe("busy");13 14 expect(syncIndicator({ status: "syncing", outcome: "relay_unreachable" }, 1)).toBe("busy");15 });1617 it("is solid green for a completed sync, with or without changes", () => {18 expect(syncIndicator({ status: "idle", outcome: "completed_changes" }, 1)).toBe("ok");19 expect(syncIndicator({ status: "idle", outcome: "completed_no_changes" }, 1)).toBe("ok");20 });2122 it("never shows green for a paired-but-unsyncable device", () => {23 24 25 expect(syncIndicator({ status: "error", outcome: "no_eligible_host" }, 2)).toBe("error");26 expect(syncIndicator({ status: "error", outcome: "runner_unavailable" }, 2)).toBe("error");27 });2829 it("stays unlit when there is nothing paired and nothing attempted", () => {30 expect(syncIndicator({}, 0)).toBe("idle");31 expect(syncIndicator({ status: "idle", outcome: "no_paired_devices" }, 0)).toBe("idle");32 });3334 it("reads a healthy pair as green before the first attempt of this session", () => {35 expect(syncIndicator({ status: "idle" }, 1)).toBe("ok");36 });3738 it("resolves every outcome to exactly one indicator, none falling through to ok", () => {39 const outcomes: SyncOutcome[] = [40 "completed_changes", "completed_no_changes", "no_paired_devices",41 "no_eligible_host", "runner_unavailable", "sync_disabled",42 "private_relay_unset", "vault_locked", "relay_unreachable",43 "handshake_failed", "vault_id_mismatch", "sync_timeout", "run_failed",44 ];45 for (const outcome of outcomes) {46 const light = syncIndicator({ status: "idle", outcome }, 1);47 expect(isSyncFailure(outcome) ? light : "not-error").not.toBe("ok");48 if (isSyncFailure(outcome)) expect(light).toBe("error");49 }50 });51});5253describe("describeSyncOutcome", () => {54 it("names what moved on a successful sync", () => {55 expect(56 describeSyncOutcome({ outcome: "completed_changes", entriesSent: 2, entriesReceived: 1 }),57 ).toBe("Synced — 2 sent, 1 received");58 });5960 it("distinguishes a successful no-op from a failure in plain English", () => {61 expect(describeSyncOutcome({ outcome: "completed_no_changes" }))62 .toBe("Up to date — nothing to send or receive");63 expect(describeSyncOutcome({ outcome: "no_eligible_host" }))64 .not.toContain("Up to date");65 });6667 it("keeps the raw diagnostic detail visible after the plain-English reason", () => {68 69 const line = describeSyncOutcome({70 outcome: "no_eligible_host",71 detail: "Pixel [type=phone, trust=direct]",72 });73 expect(line).toContain("no device here can accept a sync");74 expect(line).toContain("Pixel [type=phone, trust=direct]");75 });7677 it("says nothing has happened yet rather than implying success", () => {78 expect(describeSyncOutcome({})).toBe("Not synced yet");79 });80});8182describe("syncIndicatorLabel", () => {83 it("states the colour's claim in words", () => {84 expect(syncIndicatorLabel("error", { outcome: "vault_id_mismatch" }))85 .toBe("Sync problem — That device holds a different vault");86 });87});