refactor: extract shouldShowClearExitNode and isActiveExitNode to lib.js (TDD) — light progress on #43

- Two small pure UI predicates moved out of QML delegate
- QML now calls TailscaleLib.* (thinner glue)
- New tests + all existing tests pass
- No new state, no architecture change

Written by AI agent working for @jtmorris. Model: Grok build-0.1.
This commit is contained in:
Vybe (Coding Agent) 2026-05-22 06:33:33 +00:00
parent 0b80f5cdc8
commit 01db16cfd9
3 changed files with 24 additions and 4 deletions

View file

@ -202,7 +202,7 @@ PluginComponent {
} }
MouseArea { MouseArea {
visible: root.currentExitNode !== "" visible: TailscaleLib.shouldShowClearExitNode(root.currentExitNode)
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
hoverEnabled: true hoverEnabled: true
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -292,7 +292,7 @@ PluginComponent {
id: exitNodeButton id: exitNodeButton
text: "↗" text: "↗"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: root.currentExitNode === modelData.hostname ? Theme.primary : Theme.surfaceVariantText color: TailscaleLib.isActiveExitNode(root.currentExitNode, modelData.hostname) ? Theme.primary : Theme.surfaceVariantText
} }
} }
} }

View file

@ -76,6 +76,15 @@ function getStrings() {
} }
} }
// Light UI predicates extracted from QML (advances #43 — keeps view thin)
function shouldShowClearExitNode(currentExitNode) {
return currentExitNode !== ""
}
function isActiveExitNode(currentExitNode, hostname) {
return currentExitNode === hostname
}
function parseStatusResult(jsonText) { function parseStatusResult(jsonText) {
try { try {
const data = JSON.parse(jsonText) const data = JSON.parse(jsonText)
@ -108,5 +117,5 @@ function errorMessage(cmd) {
// 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 } module.exports = { parsePeers, makeExitNodeCommand, findActiveExitNode, errorMessage, validateClipboardTool, buildCopyCommand, nextClipboardTool, allClipboardTools, buildToggleCommand, parseStatusResult, getStrings, shouldShowClearExitNode, isActiveExitNode }
} }

View file

@ -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 } = lib const { parsePeers, makeExitNodeCommand, findActiveExitNode, errorMessage, validateClipboardTool, buildCopyCommand, nextClipboardTool, allClipboardTools, buildToggleCommand, parseStatusResult, getStrings, shouldShowClearExitNode, isActiveExitNode } = lib
test("parsePeers extracts exitNode from ExitNodeOption", () => { test("parsePeers extracts exitNode from ExitNodeOption", () => {
const peerMap = { const peerMap = {
@ -157,6 +157,17 @@ test("getStrings.copied interpolates the text", () => {
assert.ok(msg.includes("100.64.0.5")) assert.ok(msg.includes("100.64.0.5"))
}) })
test("shouldShowClearExitNode returns true only when there is a current exit node", () => {
assert.strictEqual(shouldShowClearExitNode("router"), true)
assert.strictEqual(shouldShowClearExitNode(""), false)
})
test("isActiveExitNode correctly identifies the active exit node button", () => {
assert.strictEqual(isActiveExitNode("gluetun-sjc", "gluetun-sjc"), true)
assert.strictEqual(isActiveExitNode("gluetun-sjc", "gluetun-den"), false)
assert.strictEqual(isActiveExitNode("", "router"), false)
})
// --- buildToggleCommand --- // --- buildToggleCommand ---
test("buildToggleCommand returns down command when connected", () => { test("buildToggleCommand returns down command when connected", () => {