diff --git a/tailscalectl/TailscaleWidget.qml b/tailscalectl/TailscaleWidget.qml index 63f9df2..e9d5f27 100644 --- a/tailscalectl/TailscaleWidget.qml +++ b/tailscalectl/TailscaleWidget.qml @@ -202,7 +202,7 @@ PluginComponent { } MouseArea { - visible: root.currentExitNode !== "" + visible: TailscaleLib.shouldShowClearExitNode(root.currentExitNode) cursorShape: Qt.PointingHandCursor hoverEnabled: true anchors.verticalCenter: parent.verticalCenter @@ -292,7 +292,7 @@ PluginComponent { id: exitNodeButton text: "↗" font.pixelSize: Theme.fontSizeSmall - color: root.currentExitNode === modelData.hostname ? Theme.primary : Theme.surfaceVariantText + color: TailscaleLib.isActiveExitNode(root.currentExitNode, modelData.hostname) ? Theme.primary : Theme.surfaceVariantText } } } diff --git a/tailscalectl/lib.js b/tailscalectl/lib.js index 7c9df54..e367975 100644 --- a/tailscalectl/lib.js +++ b/tailscalectl/lib.js @@ -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) { try { const data = JSON.parse(jsonText) @@ -108,5 +117,5 @@ function errorMessage(cmd) { // 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 } + module.exports = { parsePeers, makeExitNodeCommand, findActiveExitNode, errorMessage, validateClipboardTool, buildCopyCommand, nextClipboardTool, allClipboardTools, buildToggleCommand, parseStatusResult, getStrings, shouldShowClearExitNode, isActiveExitNode } } diff --git a/test/lib.test.js b/test/lib.test.js index 4d91ff9..dada9e4 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 } = lib +const { parsePeers, makeExitNodeCommand, findActiveExitNode, errorMessage, validateClipboardTool, buildCopyCommand, nextClipboardTool, allClipboardTools, buildToggleCommand, parseStatusResult, getStrings, shouldShowClearExitNode, isActiveExitNode } = lib test("parsePeers extracts exitNode from ExitNodeOption", () => { const peerMap = { @@ -157,6 +157,17 @@ test("getStrings.copied interpolates the text", () => { 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 --- test("buildToggleCommand returns down command when connected", () => {