From d6b704b8847fcc4341589f8123517575b8c06b67 Mon Sep 17 00:00:00 2001 From: vybe Date: Fri, 22 May 2026 10:19:25 +0000 Subject: [PATCH] refactor: introduce formatError centralizer + remove first batch of historical comments (TDD step 1 of first-principles plan) --- tailscalectl/lib.js | 25 ++++++++++++++++++------- test/lib.test.js | 22 +++++++++++++++++++++- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/tailscalectl/lib.js b/tailscalectl/lib.js index e367975..4dfaba1 100644 --- a/tailscalectl/lib.js +++ b/tailscalectl/lib.js @@ -33,9 +33,9 @@ function findActiveExitNode(peerMap) { var safeClipboardTools = ["dms", "wl-copy"] -// Direct argv form — no sh -c, no escaping, no future injection surface (#49) -// clipmanctl intentionally dropped (niche optional history manager, not needed on DMS + Wayland) -var clipboardTools = { + // Direct argv form — no sh -c, no escaping, no future injection surface. + // clipmanctl intentionally dropped (niche optional history manager, not needed on DMS + Wayland). + var clipboardTools = { "dms": { argv: ["dms", "cl", "copy"] }, "wl-copy": { argv: ["wl-copy"] } } @@ -76,8 +76,8 @@ function getStrings() { } } -// Light UI predicates extracted from QML (advances #43 — keeps view thin) -function shouldShowClearExitNode(currentExitNode) { + // Light UI predicates — keep the view thin. + function shouldShowClearExitNode(currentExitNode) { return currentExitNode !== "" } @@ -111,11 +111,22 @@ function errorMessage(cmd) { "disconnect": "Failed to disconnect from Tailscale", "set": "Failed to set exit node", "status": "Failed to read Tailscale status" + }; + return messages[cmd] || "Tailscale command failed"; +} + +// Central error formatting for the widget. Used by both success and error paths. +// detail is optional truncated stderr or extra context. +function formatError(action, detail) { + var base = errorMessage(action); + if (detail && detail.length > 0) { + var truncated = detail.length > 120 ? detail.slice(0, 120) : detail; + return base + " — " + truncated; } - return messages[cmd] || "Tailscale command failed" + return base; } // CommonJS export for Node.js tests (ignored by QML) if (typeof module !== "undefined" && module.exports) { - module.exports = { parsePeers, makeExitNodeCommand, findActiveExitNode, errorMessage, validateClipboardTool, buildCopyCommand, nextClipboardTool, allClipboardTools, buildToggleCommand, parseStatusResult, getStrings, shouldShowClearExitNode, isActiveExitNode } + module.exports = { parsePeers, makeExitNodeCommand, findActiveExitNode, errorMessage, formatError, validateClipboardTool, buildCopyCommand, nextClipboardTool, allClipboardTools, buildToggleCommand, parseStatusResult, getStrings, shouldShowClearExitNode, isActiveExitNode } } diff --git a/test/lib.test.js b/test/lib.test.js index cb64f07..1ca6db6 100644 --- a/test/lib.test.js +++ b/test/lib.test.js @@ -1,7 +1,7 @@ import { test } from "node:test" import assert from "node:assert" import lib from "../tailscalectl/lib.js" -const { parsePeers, makeExitNodeCommand, findActiveExitNode, errorMessage, validateClipboardTool, buildCopyCommand, nextClipboardTool, allClipboardTools, buildToggleCommand, parseStatusResult, getStrings, shouldShowClearExitNode, isActiveExitNode } = lib +const { parsePeers, makeExitNodeCommand, findActiveExitNode, errorMessage, formatError, validateClipboardTool, buildCopyCommand, nextClipboardTool, allClipboardTools, buildToggleCommand, parseStatusResult, getStrings, shouldShowClearExitNode, isActiveExitNode } = lib /* * Test coverage note for #48: @@ -240,3 +240,23 @@ test("parseStatusResult sets isConnected false for non-Running BackendState", () const state = parseStatusResult(json) assert.strictEqual(state.isConnected, false) }) + +// --- formatError (central error + detail formatting per first-principles plan) --- + +test("formatError returns base message without detail", () => { + assert.strictEqual(formatError("status"), "Failed to read Tailscale status") + assert.strictEqual(formatError("set"), "Failed to set exit node") +}) + +test("formatError appends and truncates detail", () => { + const longDetail = "x".repeat(200) + const msg = formatError("up", longDetail) + assert.ok(msg.includes("Failed to connect to Tailscale")) + assert.ok(msg.endsWith("x".repeat(120))) + assert.ok(msg.length < 200) +}) + +test("formatError handles empty or falsy detail gracefully", () => { + assert.strictEqual(formatError("down", ""), "Failed to disconnect from Tailscale") + assert.strictEqual(formatError("connect", null), "Failed to connect to Tailscale") +})