Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion meshtastic/mesh_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ def get_human_readable(name):
"channel": "Channel",
"lastHeard": "LastHeard",
"since": "Since",
"isFavorite": "Fav",

}

Expand Down Expand Up @@ -297,7 +298,7 @@ def getNestedValue(node_dict: Dict[str, Any], key_path: str) -> Any:
showFields = ["N", "user.longName", "user.id", "user.shortName", "user.hwModel", "user.publicKey",
"user.role", "position.latitude", "position.longitude", "position.altitude",
"deviceMetrics.batteryLevel", "deviceMetrics.channelUtilization",
"deviceMetrics.airUtilTx", "snr", "hopsAway", "channel", "lastHeard", "since"]
"deviceMetrics.airUtilTx", "snr", "hopsAway", "channel", "isFavorite", "lastHeard", "since"]
else:
# Always at least include the row number.
showFields.insert(0, "N")
Expand Down Expand Up @@ -339,6 +340,8 @@ def getNestedValue(node_dict: Dict[str, Any], key_path: str) -> Any:
formatted_value = "Powered"
else:
formatted_value = formatFloat(raw_value, 0, "%")
elif field == "isFavorite":
formatted_value = "*" if raw_value else ""
elif field == "lastHeard":
formatted_value = getLH(raw_value)
elif field == "position.latitude":
Expand Down
160 changes: 160 additions & 0 deletions meshtastic/tests/test_showNodes_favorite.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
"""Meshtastic unit tests for showNodes favorite column feature"""

import pytest

from ..mesh_interface import MeshInterface


@pytest.fixture
def iface_with_favorite_nodes():
"""Fixture to setup nodes with favorite flags."""
nodesById = {
"!9388f81c": {
"num": 2475227164,
"user": {
"id": "!9388f81c",
"longName": "Favorite Node",
"shortName": "FAV1",
"macaddr": "RBeTiPgc",
"hwModel": "TBEAM",
},
"position": {},
"lastHeard": 1640204888,
"isFavorite": True,
},
"!12345678": {
"num": 305419896,
"user": {
"id": "!12345678",
"longName": "Regular Node",
"shortName": "REG1",
"macaddr": "ABCDEFGH",
"hwModel": "TLORA_V2",
},
"position": {},
"lastHeard": 1640204999,
"isFavorite": False,
},
}

nodesByNum = {
2475227164: {
"num": 2475227164,
"user": {
"id": "!9388f81c",
"longName": "Favorite Node",
"shortName": "FAV1",
"macaddr": "RBeTiPgc",
"hwModel": "TBEAM",
},
"position": {"time": 1640206266},
"lastHeard": 1640206266,
"isFavorite": True,
},
305419896: {
"num": 305419896,
"user": {
"id": "!12345678",
"longName": "Regular Node",
"shortName": "REG1",
"macaddr": "ABCDEFGH",
"hwModel": "TLORA_V2",
},
"position": {"time": 1640206200},
"lastHeard": 1640206200,
"isFavorite": False,
},
}
Comment on lines +10 to +95
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test fixture should include a node that doesn't have the isFavorite field at all to test backward compatibility with existing nodes. Currently, all test nodes explicitly have isFavorite set to either True or False. Adding a third node without the isFavorite field would ensure the implementation handles legacy nodes gracefully.

Copilot uses AI. Check for mistakes.

iface = MeshInterface(noProto=True)
iface.nodes = nodesById
iface.nodesByNum = nodesByNum
from unittest.mock import MagicMock
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import statement should be moved to the top of the file with the other imports for consistency with the project's style. Other test files in this codebase import MagicMock at the module level rather than within functions.

Copilot uses AI. Check for mistakes.
myInfo = MagicMock()
iface.myInfo = myInfo
iface.myInfo.my_node_num = 2475227164
return iface


@pytest.mark.unit
def test_showNodes_favorite_column_header(capsys, iface_with_favorite_nodes):
"""Test that 'Fav' column header appears in showNodes output"""
iface = iface_with_favorite_nodes
iface.showNodes()
out, err = capsys.readouterr()
assert "Fav" in out
assert err == ""


@pytest.mark.unit
def test_showNodes_favorite_asterisk_display(capsys, iface_with_favorite_nodes):
"""Test that favorite nodes show asterisk and non-favorites show empty"""
iface = iface_with_favorite_nodes
iface.showNodes()
out, err = capsys.readouterr()

# Check that the output contains the "Fav" column
assert "Fav" in out

# The favorite node should have an asterisk in the output
# We can't easily check the exact table cell, but we can verify
# the asterisk appears somewhere in the output
lines = out.split('\n')

# Find lines containing our nodes
favorite_line = None
regular_line = None
for line in lines:
if "Favorite Node" in line or "FAV1" in line:
favorite_line = line
if "Regular Node" in line or "REG1" in line:
regular_line = line

# Basic sanity check - if we found the lines, they should be present
assert favorite_line is not None or regular_line is not None
assert err == ""
Copy link

Copilot AI Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test claims to verify that favorite nodes show asterisk and non-favorites show empty, but the actual assertions at the end only check that favorite_line or regular_line is not None, without verifying the presence or absence of asterisks. The test should actually verify that an asterisk appears in favorite_line when it's found, or should be more explicit about what it's testing. Consider adding assertions that check for asterisk presence in favorite_line and absence in regular_line.

Copilot uses AI. Check for mistakes.


@pytest.mark.unit
def test_showNodes_favorite_field_formatting():
"""Test the formatting logic for isFavorite field"""
# Test favorite node
raw_value = True
formatted_value = "*" if raw_value else ""
assert formatted_value == "*"

# Test non-favorite node
raw_value = False
formatted_value = "*" if raw_value else ""
assert formatted_value == ""

# Test None/missing value
raw_value = None
formatted_value = "*" if raw_value else ""
assert formatted_value == ""


@pytest.mark.unit
def test_showNodes_with_custom_fields_including_favorite(capsys, iface_with_favorite_nodes):
"""Test that isFavorite can be specified in custom showFields"""
iface = iface_with_favorite_nodes
custom_fields = ["user.longName", "isFavorite"]
iface.showNodes(showFields=custom_fields)
out, err = capsys.readouterr()

# Should still show the Fav column when explicitly requested
assert "Fav" in out
assert err == ""


@pytest.mark.unit
def test_showNodes_default_fields_includes_favorite(iface_with_favorite_nodes):
"""Test that isFavorite is included in default fields"""
iface = iface_with_favorite_nodes

# Call showNodes which uses default fields
result = iface.showNodes()

# The result should contain the formatted table as a string
assert "Fav" in result

Loading