refactor: introduce formatError centralizer + remove first batch of historical comments (TDD step 1 of first-principles plan)
This commit is contained in:
parent
0283ce6136
commit
d6b704b884
2 changed files with 39 additions and 8 deletions
|
|
@ -33,8 +33,8 @@ function findActiveExitNode(peerMap) {
|
||||||
|
|
||||||
var safeClipboardTools = ["dms", "wl-copy"]
|
var safeClipboardTools = ["dms", "wl-copy"]
|
||||||
|
|
||||||
// Direct argv form — no sh -c, no escaping, no future injection surface (#49)
|
// Direct argv form — no sh -c, no escaping, no future injection surface.
|
||||||
// clipmanctl intentionally dropped (niche optional history manager, not needed on DMS + Wayland)
|
// clipmanctl intentionally dropped (niche optional history manager, not needed on DMS + Wayland).
|
||||||
var clipboardTools = {
|
var clipboardTools = {
|
||||||
"dms": { argv: ["dms", "cl", "copy"] },
|
"dms": { argv: ["dms", "cl", "copy"] },
|
||||||
"wl-copy": { argv: ["wl-copy"] }
|
"wl-copy": { argv: ["wl-copy"] }
|
||||||
|
|
@ -76,7 +76,7 @@ function getStrings() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Light UI predicates extracted from QML (advances #43 — keeps view thin)
|
// Light UI predicates — keep the view thin.
|
||||||
function shouldShowClearExitNode(currentExitNode) {
|
function shouldShowClearExitNode(currentExitNode) {
|
||||||
return currentExitNode !== ""
|
return currentExitNode !== ""
|
||||||
}
|
}
|
||||||
|
|
@ -111,11 +111,22 @@ function errorMessage(cmd) {
|
||||||
"disconnect": "Failed to disconnect from Tailscale",
|
"disconnect": "Failed to disconnect from Tailscale",
|
||||||
"set": "Failed to set exit node",
|
"set": "Failed to set exit node",
|
||||||
"status": "Failed to read Tailscale status"
|
"status": "Failed to read Tailscale status"
|
||||||
|
};
|
||||||
|
return messages[cmd] || "Tailscale command failed";
|
||||||
}
|
}
|
||||||
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 base;
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommonJS export for Node.js tests (ignored by QML)
|
// CommonJS export for Node.js tests (ignored by QML)
|
||||||
if (typeof module !== "undefined" && module.exports) {
|
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 }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { test } from "node:test"
|
import { test } from "node:test"
|
||||||
import assert from "node:assert"
|
import assert from "node:assert"
|
||||||
import lib from "../tailscalectl/lib.js"
|
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:
|
* Test coverage note for #48:
|
||||||
|
|
@ -240,3 +240,23 @@ test("parseStatusResult sets isConnected false for non-Running BackendState", ()
|
||||||
const state = parseStatusResult(json)
|
const state = parseStatusResult(json)
|
||||||
assert.strictEqual(state.isConnected, false)
|
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")
|
||||||
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue