mirror of
https://github.com/meshtastic/Meshtastic-Apple.git
synced 2026-04-20 22:13:56 +00:00
feat/persist-node-list-filters
- move test file to correct location - get test working
This commit is contained in:
parent
8e06db006b
commit
e65f9bbec8
2 changed files with 330 additions and 245 deletions
|
|
@ -7,7 +7,7 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
05DCA1AF2F646B3B00D0724C /* NodeFilterParametersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05DCA1AE2F646B3B00D0724C /* NodeFilterParametersTests.swift */; };
|
||||
0516B3FE2F68892000D0FC40 /* NodeFilterParametersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05DCA1AE2F646B3B00D0724C /* NodeFilterParametersTests.swift */; };
|
||||
102B5EAB2E172F41003D191E /* DatadogCore in Frameworks */ = {isa = PBXBuildFile; productRef = 102B5EAA2E172F41003D191E /* DatadogCore */; };
|
||||
102B5EAD2E172F41003D191E /* DatadogCrashReporting in Frameworks */ = {isa = PBXBuildFile; productRef = 102B5EAC2E172F41003D191E /* DatadogCrashReporting */; };
|
||||
102B5EAF2E172F41003D191E /* DatadogLogs in Frameworks */ = {isa = PBXBuildFile; productRef = 102B5EAE2E172F41003D191E /* DatadogLogs */; };
|
||||
|
|
@ -1661,6 +1661,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
25F5D5D12C4375DF008036E3 /* RouterTests.swift in Sources */,
|
||||
0516B3FE2F68892000D0FC40 /* NodeFilterParametersTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -1884,7 +1885,6 @@
|
|||
DD6F65742C6CB80A0053C113 /* View.swift in Sources */,
|
||||
DD1933762B0835D500771CD5 /* PositionAltitudeChart.swift in Sources */,
|
||||
DD415828285859C4009B0E59 /* TelemetryConfig.swift in Sources */,
|
||||
05DCA1AF2F646B3B00D0724C /* NodeFilterParametersTests.swift in Sources */,
|
||||
DD6D5A332CA1178300ED3032 /* TraceRoute.swift in Sources */,
|
||||
DDB6CCFB2AAF805100945AF6 /* NodeMapSwiftUI.swift in Sources */,
|
||||
BCB613872C69A0FB00485544 /* AppIntentErrors.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -1,332 +1,417 @@
|
|||
//
|
||||
// NodeFilterParametersTests.swift
|
||||
// MeshtasticTests
|
||||
// Meshtastic
|
||||
//
|
||||
// Created on 3/13/26.
|
||||
// Created on 3/16/26.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@testable import Meshtastic
|
||||
|
||||
@MainActor
|
||||
final class NodeFilterParametersTests: XCTestCase {
|
||||
class NodeFilterParametersTests: XCTestCase {
|
||||
|
||||
var sut: NodeFilterParameters!
|
||||
let testSuiteName = "NodeFilterParametersTests"
|
||||
// MARK: - Initialization Tests
|
||||
|
||||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
func testDefaultInitialization() async throws {
|
||||
// Clean up UserDefaults before test
|
||||
clearAllFilterDefaults()
|
||||
|
||||
// Clear UserDefaults before each test to ensure clean state
|
||||
clearUserDefaults()
|
||||
let filters = NodeFilterParameters()
|
||||
|
||||
// Create a fresh instance
|
||||
sut = NodeFilterParameters()
|
||||
XCTAssertEqual(filters.searchText, "")
|
||||
XCTAssertEqual(filters.isOnline, false)
|
||||
XCTAssertEqual(filters.isPkiEncrypted, false)
|
||||
XCTAssertEqual(filters.isFavorite, false)
|
||||
XCTAssertEqual(filters.isIgnored, false)
|
||||
XCTAssertEqual(filters.isEnvironment, false)
|
||||
XCTAssertEqual(filters.distanceFilter, false)
|
||||
XCTAssertEqual(filters.maxDistance, 800_000)
|
||||
XCTAssertEqual(filters.hopsAway, -1.0)
|
||||
XCTAssertEqual(filters.roleFilter, false)
|
||||
XCTAssertTrue(filters.deviceRoles.isEmpty)
|
||||
XCTAssertEqual(filters.viaLora, true)
|
||||
XCTAssertEqual(filters.viaMqtt, true)
|
||||
}
|
||||
|
||||
override func tearDown() async throws {
|
||||
sut = nil
|
||||
clearUserDefaults()
|
||||
try await super.tearDown()
|
||||
}
|
||||
func testInitializationWithPersistedDeviceRoles() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
private func clearUserDefaults() {
|
||||
let defaults = UserDefaults.standard
|
||||
let keys = [
|
||||
"nodeFilter.searchText",
|
||||
"nodeFilter.isOnline",
|
||||
"nodeFilter.isPkiEncrypted",
|
||||
"nodeFilter.isFavorite",
|
||||
"nodeFilter.isIgnored",
|
||||
"nodeFilter.isEnvironment",
|
||||
"nodeFilter.distanceFilter",
|
||||
"nodeFilter.maxDistance",
|
||||
"nodeFilter.hopsAway",
|
||||
"nodeFilter.roleFilter",
|
||||
"nodeFilter.deviceRoles",
|
||||
"nodeFilter.viaLora",
|
||||
"nodeFilter.viaMqtt"
|
||||
]
|
||||
keys.forEach { defaults.removeObject(forKey: $0) }
|
||||
}
|
||||
// Store device roles in UserDefaults
|
||||
let expectedRoles = [1, 2, 3, 5, 8]
|
||||
UserDefaults.standard.set(expectedRoles, forKey: "nodeFilter.deviceRoles")
|
||||
|
||||
func testDefaultValues() {
|
||||
XCTAssertEqual(sut.searchText, "", "Search text should default to empty string")
|
||||
XCTAssertFalse(sut.isOnline, "isOnline should default to false")
|
||||
XCTAssertFalse(sut.isPkiEncrypted, "isPkiEncrypted should default to false")
|
||||
XCTAssertFalse(sut.isFavorite, "isFavorite should default to false")
|
||||
XCTAssertFalse(sut.isIgnored, "isIgnored should default to false")
|
||||
XCTAssertFalse(sut.isEnvironment, "isEnvironment should default to false")
|
||||
XCTAssertFalse(sut.distanceFilter, "distanceFilter should default to false")
|
||||
XCTAssertEqual(sut.maxDistance, 800_000, "maxDistance should default to 800,000")
|
||||
XCTAssertEqual(sut.hopsAway, -1.0, "hopsAway should default to -1.0")
|
||||
XCTAssertFalse(sut.roleFilter, "roleFilter should default to false")
|
||||
XCTAssertTrue(sut.deviceRoles.isEmpty, "deviceRoles should default to empty set")
|
||||
XCTAssertTrue(sut.viaLora, "viaLora should default to true")
|
||||
XCTAssertTrue(sut.viaMqtt, "viaMqtt should default to true")
|
||||
}
|
||||
let filters = NodeFilterParameters()
|
||||
|
||||
func testSearchTextPersistence() {
|
||||
sut.searchText = "Test Node"
|
||||
|
||||
// Create new instance to test persistence
|
||||
let newInstance = NodeFilterParameters()
|
||||
XCTAssertEqual(newInstance.searchText, "Test Node", "Search text should persist")
|
||||
XCTAssertEqual(filters.deviceRoles, Set(expectedRoles))
|
||||
}
|
||||
|
||||
func testBooleanFiltersPersistence() {
|
||||
sut.isOnline = true
|
||||
sut.isPkiEncrypted = true
|
||||
sut.isFavorite = true
|
||||
sut.isIgnored = true
|
||||
sut.isEnvironment = true
|
||||
// MARK: - @AppStorage Persistence Tests
|
||||
|
||||
func testSearchTextPersistence() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
let newInstance = NodeFilterParameters()
|
||||
XCTAssertTrue(newInstance.isOnline, "isOnline should persist")
|
||||
XCTAssertTrue(newInstance.isPkiEncrypted, "isPkiEncrypted should persist")
|
||||
XCTAssertTrue(newInstance.isFavorite, "isFavorite should persist")
|
||||
XCTAssertTrue(newInstance.isIgnored, "isIgnored should persist")
|
||||
XCTAssertTrue(newInstance.isEnvironment, "isEnvironment should persist")
|
||||
let filters1 = NodeFilterParameters()
|
||||
filters1.searchText = "Test Node"
|
||||
|
||||
let filters2 = NodeFilterParameters()
|
||||
XCTAssertEqual(filters2.searchText, "Test Node")
|
||||
}
|
||||
|
||||
func testDistanceFilterPersistence() {
|
||||
sut.distanceFilter = true
|
||||
sut.maxDistance = 50_000
|
||||
func testBooleanFiltersPersistence() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
let newInstance = NodeFilterParameters()
|
||||
XCTAssertTrue(newInstance.distanceFilter, "distanceFilter should persist")
|
||||
XCTAssertEqual(newInstance.maxDistance, 50_000, "maxDistance should persist")
|
||||
let filters1 = NodeFilterParameters()
|
||||
filters1.isOnline = true
|
||||
filters1.isPkiEncrypted = true
|
||||
filters1.isFavorite = true
|
||||
filters1.isIgnored = true
|
||||
filters1.isEnvironment = true
|
||||
filters1.distanceFilter = true
|
||||
filters1.roleFilter = true
|
||||
|
||||
let filters2 = NodeFilterParameters()
|
||||
XCTAssertEqual(filters2.isOnline, true)
|
||||
XCTAssertEqual(filters2.isPkiEncrypted, true)
|
||||
XCTAssertEqual(filters2.isFavorite, true)
|
||||
XCTAssertEqual(filters2.isIgnored, true)
|
||||
XCTAssertEqual(filters2.isEnvironment, true)
|
||||
XCTAssertEqual(filters2.distanceFilter, true)
|
||||
XCTAssertEqual(filters2.roleFilter, true)
|
||||
}
|
||||
|
||||
func testHopsAwayPersistence() {
|
||||
sut.hopsAway = 3.0
|
||||
func testNumericFiltersPersistence() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
let newInstance = NodeFilterParameters()
|
||||
XCTAssertEqual(newInstance.hopsAway, 3.0, "hopsAway should persist")
|
||||
let filters1 = NodeFilterParameters()
|
||||
filters1.maxDistance = 500_000
|
||||
filters1.hopsAway = 3.0
|
||||
|
||||
let filters2 = NodeFilterParameters()
|
||||
XCTAssertEqual(filters2.maxDistance, 500_000)
|
||||
XCTAssertEqual(filters2.hopsAway, 3.0)
|
||||
}
|
||||
|
||||
func testRoleFilterPersistence() {
|
||||
sut.roleFilter = true
|
||||
// MARK: - Device Roles Tests
|
||||
|
||||
func testDeviceRolesPersistence() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
let newInstance = NodeFilterParameters()
|
||||
XCTAssertTrue(newInstance.roleFilter, "roleFilter should persist")
|
||||
}
|
||||
let filters1 = NodeFilterParameters()
|
||||
filters1.deviceRoles = [1, 3, 5, 7]
|
||||
|
||||
func testDeviceRolesInitiallyEmpty() {
|
||||
XCTAssertTrue(sut.deviceRoles.isEmpty, "deviceRoles should be empty initially")
|
||||
// Verify it's stored in UserDefaults
|
||||
let storedRoles = UserDefaults.standard.array(forKey: "nodeFilter.deviceRoles") as? [Int]
|
||||
XCTAssertNotNil(storedRoles)
|
||||
XCTAssertEqual(Set(storedRoles!), Set([1, 3, 5, 7]))
|
||||
|
||||
// Verify it persists to new instance
|
||||
let filters2 = NodeFilterParameters()
|
||||
XCTAssertEqual(filters2.deviceRoles, Set([1, 3, 5, 7]))
|
||||
}
|
||||
|
||||
func testDeviceRolesPersistence() {
|
||||
// Add some roles
|
||||
sut.deviceRoles = [0, 1, 2]
|
||||
func testAddingDeviceRoles() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
// Verify they're stored in UserDefaults
|
||||
let stored = UserDefaults.standard.array(forKey: "nodeFilter.deviceRoles") as? [Int]
|
||||
XCTAssertNotNil(stored, "deviceRoles should be stored in UserDefaults")
|
||||
XCTAssertEqual(Set(stored ?? []), Set([0, 1, 2]), "Stored roles should match")
|
||||
let filters = NodeFilterParameters()
|
||||
filters.deviceRoles.insert(2)
|
||||
filters.deviceRoles.insert(4)
|
||||
filters.deviceRoles.insert(6)
|
||||
|
||||
// Create new instance to test persistence
|
||||
let newInstance = NodeFilterParameters()
|
||||
XCTAssertEqual(newInstance.deviceRoles, [0, 1, 2], "deviceRoles should persist")
|
||||
let newFilters = NodeFilterParameters()
|
||||
XCTAssertTrue(newFilters.deviceRoles.contains(2))
|
||||
XCTAssertTrue(newFilters.deviceRoles.contains(4))
|
||||
XCTAssertTrue(newFilters.deviceRoles.contains(6))
|
||||
XCTAssertEqual(newFilters.deviceRoles.count, 3)
|
||||
}
|
||||
|
||||
func testDeviceRolesAddAndRemove() {
|
||||
XCTAssertTrue(sut.deviceRoles.isEmpty, "Should start empty")
|
||||
func testRemovingDeviceRoles() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
// Add roles
|
||||
sut.deviceRoles.insert(1)
|
||||
sut.deviceRoles.insert(3)
|
||||
sut.deviceRoles.insert(5)
|
||||
XCTAssertEqual(sut.deviceRoles.count, 3, "Should have 3 roles")
|
||||
let filters1 = NodeFilterParameters()
|
||||
filters1.deviceRoles = [1, 2, 3, 4, 5]
|
||||
|
||||
// Remove a role
|
||||
sut.deviceRoles.remove(3)
|
||||
XCTAssertEqual(sut.deviceRoles.count, 2, "Should have 2 roles after removal")
|
||||
XCTAssertTrue(sut.deviceRoles.contains(1), "Should still contain role 1")
|
||||
XCTAssertTrue(sut.deviceRoles.contains(5), "Should still contain role 5")
|
||||
XCTAssertFalse(sut.deviceRoles.contains(3), "Should not contain removed role 3")
|
||||
filters1.deviceRoles.remove(2)
|
||||
filters1.deviceRoles.remove(4)
|
||||
|
||||
// Verify persistence after changes
|
||||
let newInstance = NodeFilterParameters()
|
||||
XCTAssertEqual(newInstance.deviceRoles, sut.deviceRoles, "Changes should persist")
|
||||
}
|
||||
|
||||
func testViaLoraAndMqttBothTrueByDefault() {
|
||||
XCTAssertTrue(sut.viaLora, "viaLora should default to true")
|
||||
XCTAssertTrue(sut.viaMqtt, "viaMqtt should default to true")
|
||||
let filters2 = NodeFilterParameters()
|
||||
XCTAssertEqual(filters2.deviceRoles, Set([1, 3, 5]))
|
||||
XCTAssertFalse(filters2.deviceRoles.contains(2))
|
||||
XCTAssertFalse(filters2.deviceRoles.contains(4))
|
||||
}
|
||||
|
||||
func testCanSetViaLoraToFalseWhenMqttIsTrue() {
|
||||
sut.viaLora = false
|
||||
func testEmptyDeviceRolesPersistence() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
XCTAssertFalse(sut.viaLora, "viaLora should be false")
|
||||
XCTAssertTrue(sut.viaMqtt, "viaMqtt should remain true")
|
||||
let filters1 = NodeFilterParameters()
|
||||
filters1.deviceRoles = [1, 2, 3]
|
||||
|
||||
// Clear the set
|
||||
filters1.deviceRoles = []
|
||||
|
||||
let filters2 = NodeFilterParameters()
|
||||
XCTAssertTrue(filters2.deviceRoles.isEmpty)
|
||||
}
|
||||
|
||||
func testCanSetViaMqttToFalseWhenLoraIsTrue() {
|
||||
sut.viaMqtt = false
|
||||
// MARK: - Via Lora/MQTT Enforcement Tests
|
||||
|
||||
func testViaLoraEnforcesViaMqtt() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
XCTAssertFalse(sut.viaMqtt, "viaMqtt should be false")
|
||||
XCTAssertTrue(sut.viaLora, "viaLora should remain true")
|
||||
let filters = NodeFilterParameters()
|
||||
|
||||
// Start with both true
|
||||
XCTAssertEqual(filters.viaLora, true)
|
||||
XCTAssertEqual(filters.viaMqtt, true)
|
||||
|
||||
// Set viaLora to false
|
||||
filters.viaLora = false
|
||||
|
||||
// viaMqtt should remain true
|
||||
XCTAssertEqual(filters.viaLora, false)
|
||||
XCTAssertEqual(filters.viaMqtt, true)
|
||||
|
||||
// Try to set viaMqtt to false - it should enforce viaLora to true
|
||||
filters.viaMqtt = false
|
||||
|
||||
XCTAssertEqual(filters.viaLora, true)
|
||||
XCTAssertEqual(filters.viaMqtt, false)
|
||||
}
|
||||
|
||||
func testEnforcesAtLeastOneViaLoraOrMqtt_WhenSettingLoraFalse() {
|
||||
// First set MQTT to false
|
||||
sut.viaMqtt = false
|
||||
XCTAssertFalse(sut.viaMqtt, "viaMqtt should be false")
|
||||
XCTAssertTrue(sut.viaLora, "viaLora should be true")
|
||||
func testViaMqttEnforcesViaLora() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
// Try to set LoRa to false - should enforce MQTT back to true
|
||||
sut.viaLora = false
|
||||
let filters = NodeFilterParameters()
|
||||
|
||||
XCTAssertFalse(sut.viaLora, "viaLora should be false")
|
||||
XCTAssertTrue(sut.viaMqtt, "viaMqtt should be enforced to true")
|
||||
// Start with both true
|
||||
XCTAssertEqual(filters.viaLora, true)
|
||||
XCTAssertEqual(filters.viaMqtt, true)
|
||||
|
||||
// Set viaMqtt to false
|
||||
filters.viaMqtt = false
|
||||
|
||||
// viaLora should remain true
|
||||
XCTAssertEqual(filters.viaLora, true)
|
||||
XCTAssertEqual(filters.viaMqtt, false)
|
||||
|
||||
// Try to set viaLora to false - it should enforce viaMqtt to true
|
||||
filters.viaLora = false
|
||||
|
||||
XCTAssertEqual(filters.viaLora, false)
|
||||
XCTAssertEqual(filters.viaMqtt, true)
|
||||
}
|
||||
|
||||
func testEnforcesAtLeastOneViaLoraOrMqtt_WhenSettingMqttFalse() {
|
||||
// First set LoRa to false
|
||||
sut.viaLora = false
|
||||
XCTAssertFalse(sut.viaLora, "viaLora should be false")
|
||||
XCTAssertTrue(sut.viaMqtt, "viaMqtt should be true")
|
||||
func testBothViaTrue() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
// Try to set MQTT to false - should enforce LoRa back to true
|
||||
sut.viaMqtt = false
|
||||
let filters = NodeFilterParameters()
|
||||
filters.viaLora = true
|
||||
filters.viaMqtt = true
|
||||
|
||||
XCTAssertFalse(sut.viaMqtt, "viaMqtt should be false")
|
||||
XCTAssertTrue(sut.viaLora, "viaLora should be enforced to true")
|
||||
XCTAssertEqual(filters.viaLora, true)
|
||||
XCTAssertEqual(filters.viaMqtt, true)
|
||||
}
|
||||
|
||||
func testViaLoraAndMqttPersistence() {
|
||||
sut.viaLora = false
|
||||
sut.viaMqtt = true
|
||||
func testViaSettingsPersistence() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
let newInstance = NodeFilterParameters()
|
||||
XCTAssertFalse(newInstance.viaLora, "viaLora state should persist")
|
||||
XCTAssertTrue(newInstance.viaMqtt, "viaMqtt state should persist")
|
||||
let filters1 = NodeFilterParameters()
|
||||
filters1.viaLora = false
|
||||
// viaMqtt should be enforced to true
|
||||
|
||||
let filters2 = NodeFilterParameters()
|
||||
XCTAssertEqual(filters2.viaLora, false)
|
||||
XCTAssertEqual(filters2.viaMqtt, true)
|
||||
}
|
||||
|
||||
func testCannotHaveBothViaFalse() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
let filters = NodeFilterParameters()
|
||||
|
||||
// Set viaLora to false first
|
||||
filters.viaLora = false
|
||||
XCTAssertEqual(filters.viaLora, false)
|
||||
XCTAssertEqual(filters.viaMqtt, true)
|
||||
|
||||
// Try to set viaMqtt to false
|
||||
filters.viaMqtt = false
|
||||
|
||||
// viaLora should be enforced back to true
|
||||
XCTAssertEqual(filters.viaLora, true)
|
||||
XCTAssertEqual(filters.viaMqtt, false)
|
||||
}
|
||||
|
||||
// MARK: - ObservableObject Tests
|
||||
|
||||
func testObjectWillChangeTriggeredOnViaLoraChange() {
|
||||
let expectation = XCTestExpectation(description: "objectWillChange should trigger")
|
||||
func testObjectWillChange() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
let cancellable = sut.objectWillChange.sink {
|
||||
expectation.fulfill()
|
||||
let filters = NodeFilterParameters()
|
||||
var changeCount = 0
|
||||
|
||||
let cancellable = filters.objectWillChange.sink {
|
||||
changeCount += 1
|
||||
}
|
||||
|
||||
sut.viaLora = false
|
||||
// Modify various properties
|
||||
filters.searchText = "Test"
|
||||
filters.isOnline = true
|
||||
filters.deviceRoles.insert(1)
|
||||
filters.viaLora = false
|
||||
|
||||
// Should have triggered changes
|
||||
XCTAssertGreaterThan(changeCount, 0)
|
||||
|
||||
wait(for: [expectation], timeout: 0.1)
|
||||
cancellable.cancel()
|
||||
}
|
||||
|
||||
func testObjectWillChangeTriggeredOnViaMqttChange() {
|
||||
let expectation = XCTestExpectation(description: "objectWillChange should trigger")
|
||||
// MARK: - Edge Cases
|
||||
|
||||
func testLargeMaxDistance() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
let cancellable = sut.objectWillChange.sink {
|
||||
expectation.fulfill()
|
||||
}
|
||||
let filters = NodeFilterParameters()
|
||||
filters.maxDistance = 10_000_000
|
||||
|
||||
sut.viaMqtt = false
|
||||
|
||||
wait(for: [expectation], timeout: 0.1)
|
||||
cancellable.cancel()
|
||||
let newFilters = NodeFilterParameters()
|
||||
XCTAssertEqual(newFilters.maxDistance, 10_000_000)
|
||||
}
|
||||
|
||||
func testObjectWillChangeTriggeredOnDeviceRolesChange() {
|
||||
let expectation = XCTestExpectation(description: "objectWillChange should trigger")
|
||||
expectation.expectedFulfillmentCount = 1
|
||||
func testNegativeHopsAway() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
let cancellable = sut.objectWillChange.sink {
|
||||
expectation.fulfill()
|
||||
}
|
||||
let filters = NodeFilterParameters()
|
||||
filters.hopsAway = -1.0
|
||||
|
||||
sut.deviceRoles = [1, 2, 3]
|
||||
|
||||
wait(for: [expectation], timeout: 0.1)
|
||||
cancellable.cancel()
|
||||
}
|
||||
|
||||
func testMaxDistanceBoundaryValues() {
|
||||
sut.maxDistance = 0
|
||||
XCTAssertEqual(sut.maxDistance, 0, "Should handle zero distance")
|
||||
|
||||
sut.maxDistance = Double.greatestFiniteMagnitude
|
||||
XCTAssertEqual(sut.maxDistance, Double.greatestFiniteMagnitude, "Should handle large distances")
|
||||
let newFilters = NodeFilterParameters()
|
||||
XCTAssertEqual(newFilters.hopsAway, -1.0)
|
||||
}
|
||||
|
||||
func testHopsAwayBoundaryValues() {
|
||||
sut.hopsAway = -1.0
|
||||
XCTAssertEqual(sut.hopsAway, -1.0, "Should handle -1 (all hops)")
|
||||
func testZeroHopsAway() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
sut.hopsAway = 0.0
|
||||
XCTAssertEqual(sut.hopsAway, 0.0, "Should handle 0 (direct)")
|
||||
let filters = NodeFilterParameters()
|
||||
filters.hopsAway = 0.0
|
||||
|
||||
sut.hopsAway = 7.0
|
||||
XCTAssertEqual(sut.hopsAway, 7.0, "Should handle maximum hops")
|
||||
let newFilters = NodeFilterParameters()
|
||||
XCTAssertEqual(newFilters.hopsAway, 0.0)
|
||||
}
|
||||
|
||||
func testSearchTextWithSpecialCharacters() {
|
||||
let specialStrings = [
|
||||
"Test Node #1",
|
||||
"Node@123",
|
||||
"Node with spaces",
|
||||
"Node_with_underscores",
|
||||
"Node-with-dashes",
|
||||
"Node.with.dots",
|
||||
"🎯 Node with emoji"
|
||||
]
|
||||
func testSpecialCharactersInSearchText() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
for testString in specialStrings {
|
||||
sut.searchText = testString
|
||||
XCTAssertEqual(sut.searchText, testString, "Should handle: \(testString)")
|
||||
}
|
||||
let filters = NodeFilterParameters()
|
||||
filters.searchText = "Test!@#$%^&*()_+-=[]{}|;':\",./<>?"
|
||||
|
||||
let newFilters = NodeFilterParameters()
|
||||
XCTAssertEqual(newFilters.searchText, "Test!@#$%^&*()_+-=[]{}|;':\",./<>?")
|
||||
}
|
||||
|
||||
func testDeviceRolesWithDuplicates() {
|
||||
sut.deviceRoles = [1, 2, 3]
|
||||
sut.deviceRoles.insert(2) // Try to insert duplicate
|
||||
func testUnicodeInSearchText() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
XCTAssertEqual(sut.deviceRoles.count, 3, "Set should not contain duplicates")
|
||||
XCTAssertTrue(sut.deviceRoles.contains(1), "Should contain 1")
|
||||
XCTAssertTrue(sut.deviceRoles.contains(2), "Should contain 2")
|
||||
XCTAssertTrue(sut.deviceRoles.contains(3), "Should contain 3")
|
||||
let filters = NodeFilterParameters()
|
||||
filters.searchText = "测试 Тест 🎉🚀"
|
||||
|
||||
let newFilters = NodeFilterParameters()
|
||||
XCTAssertEqual(newFilters.searchText, "测试 Тест 🎉🚀")
|
||||
}
|
||||
|
||||
func testMultipleFiltersActive() {
|
||||
sut.searchText = "Test"
|
||||
sut.isOnline = true
|
||||
sut.isFavorite = true
|
||||
sut.distanceFilter = true
|
||||
sut.maxDistance = 100_000
|
||||
sut.hopsAway = 2.0
|
||||
sut.roleFilter = true
|
||||
sut.deviceRoles = [0, 1]
|
||||
sut.viaLora = true
|
||||
sut.viaMqtt = false
|
||||
func testLongSearchText() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
// Verify all settings
|
||||
XCTAssertEqual(sut.searchText, "Test")
|
||||
XCTAssertTrue(sut.isOnline)
|
||||
XCTAssertTrue(sut.isFavorite)
|
||||
XCTAssertTrue(sut.distanceFilter)
|
||||
XCTAssertEqual(sut.maxDistance, 100_000)
|
||||
XCTAssertEqual(sut.hopsAway, 2.0)
|
||||
XCTAssertTrue(sut.roleFilter)
|
||||
XCTAssertEqual(sut.deviceRoles, [0, 1])
|
||||
XCTAssertTrue(sut.viaLora)
|
||||
XCTAssertFalse(sut.viaMqtt)
|
||||
let filters = NodeFilterParameters()
|
||||
let longText = String(repeating: "A", count: 1000)
|
||||
filters.searchText = longText
|
||||
|
||||
// Verify persistence
|
||||
let newInstance = NodeFilterParameters()
|
||||
XCTAssertEqual(newInstance.searchText, "Test")
|
||||
XCTAssertTrue(newInstance.isOnline)
|
||||
XCTAssertTrue(newInstance.isFavorite)
|
||||
XCTAssertTrue(newInstance.distanceFilter)
|
||||
XCTAssertEqual(newInstance.maxDistance, 100_000)
|
||||
XCTAssertEqual(newInstance.hopsAway, 2.0)
|
||||
XCTAssertTrue(newInstance.roleFilter)
|
||||
XCTAssertEqual(newInstance.deviceRoles, [0, 1])
|
||||
XCTAssertTrue(newInstance.viaLora)
|
||||
XCTAssertFalse(newInstance.viaMqtt)
|
||||
let newFilters = NodeFilterParameters()
|
||||
XCTAssertEqual(newFilters.searchText, longText)
|
||||
}
|
||||
|
||||
func testLargeDeviceRolesSet() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
let filters = NodeFilterParameters()
|
||||
filters.deviceRoles = Set(0...100)
|
||||
|
||||
let newFilters = NodeFilterParameters()
|
||||
XCTAssertEqual(newFilters.deviceRoles, Set(0...100))
|
||||
XCTAssertEqual(newFilters.deviceRoles.count, 101)
|
||||
}
|
||||
|
||||
// MARK: - Reset/Clear Tests
|
||||
|
||||
func testResetAllFilters() async throws {
|
||||
clearAllFilterDefaults()
|
||||
|
||||
let filters = NodeFilterParameters()
|
||||
|
||||
// Set all values to non-default
|
||||
filters.searchText = "Test"
|
||||
filters.isOnline = true
|
||||
filters.isPkiEncrypted = true
|
||||
filters.isFavorite = true
|
||||
filters.isIgnored = true
|
||||
filters.isEnvironment = true
|
||||
filters.distanceFilter = true
|
||||
filters.maxDistance = 500_000
|
||||
filters.hopsAway = 5.0
|
||||
filters.roleFilter = true
|
||||
filters.deviceRoles = [1, 2, 3]
|
||||
filters.viaLora = false
|
||||
|
||||
// Reset to defaults
|
||||
filters.searchText = ""
|
||||
filters.isOnline = false
|
||||
filters.isPkiEncrypted = false
|
||||
filters.isFavorite = false
|
||||
filters.isIgnored = false
|
||||
filters.isEnvironment = false
|
||||
filters.distanceFilter = false
|
||||
filters.maxDistance = 800_000
|
||||
filters.hopsAway = -1.0
|
||||
filters.roleFilter = false
|
||||
filters.deviceRoles = []
|
||||
filters.viaLora = true
|
||||
filters.viaMqtt = true
|
||||
|
||||
// Verify all are back to defaults
|
||||
let newFilters = NodeFilterParameters()
|
||||
XCTAssertEqual(newFilters.searchText, "")
|
||||
XCTAssertEqual(newFilters.isOnline, false)
|
||||
XCTAssertEqual(newFilters.isPkiEncrypted, false)
|
||||
XCTAssertEqual(newFilters.isFavorite, false)
|
||||
XCTAssertEqual(newFilters.isIgnored, false)
|
||||
XCTAssertEqual(newFilters.isEnvironment, false)
|
||||
XCTAssertEqual(newFilters.distanceFilter, false)
|
||||
XCTAssertEqual(newFilters.maxDistance, 800_000)
|
||||
XCTAssertEqual(newFilters.hopsAway, -1.0)
|
||||
XCTAssertEqual(newFilters.roleFilter, false)
|
||||
XCTAssertTrue(newFilters.deviceRoles.isEmpty)
|
||||
XCTAssertEqual(newFilters.viaLora, true)
|
||||
XCTAssertEqual(newFilters.viaMqtt, true)
|
||||
}
|
||||
|
||||
// MARK: - Helper Functions
|
||||
|
||||
/// Clears all NodeFilter-related UserDefaults to ensure clean test state
|
||||
private func clearAllFilterDefaults() {
|
||||
let defaults = UserDefaults.standard
|
||||
defaults.removeObject(forKey: "nodeFilter.searchText")
|
||||
defaults.removeObject(forKey: "nodeFilter.isOnline")
|
||||
defaults.removeObject(forKey: "nodeFilter.isPkiEncrypted")
|
||||
defaults.removeObject(forKey: "nodeFilter.isFavorite")
|
||||
defaults.removeObject(forKey: "nodeFilter.isIgnored")
|
||||
defaults.removeObject(forKey: "nodeFilter.isEnvironment")
|
||||
defaults.removeObject(forKey: "nodeFilter.distanceFilter")
|
||||
defaults.removeObject(forKey: "nodeFilter.maxDistance")
|
||||
defaults.removeObject(forKey: "nodeFilter.hopsAway")
|
||||
defaults.removeObject(forKey: "nodeFilter.roleFilter")
|
||||
defaults.removeObject(forKey: "nodeFilter.deviceRoles")
|
||||
defaults.removeObject(forKey: "nodeFilter.viaLora")
|
||||
defaults.removeObject(forKey: "nodeFilter.viaMqtt")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue