From 455fe73f69af38f47c112e7c742e11cb39333314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pantalea=CC=83o?= <5808343+bgoncal@users.noreply.github.com> Date: Tue, 28 Jul 2026 10:50:42 -0300 Subject: [PATCH 1/4] Deprecate friendlyName in favor of list_for_display --- Source/Data/HAEntity.swift | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Source/Data/HAEntity.swift b/Source/Data/HAEntity.swift index a58ef49a..c53f61d5 100644 --- a/Source/Data/HAEntity.swift +++ b/Source/Data/HAEntity.swift @@ -115,7 +115,19 @@ public struct HAEntityAttributes { /// This contains all keys and values received, including those not parsed or handled otherwise public var dictionary: [String: Any] - /// The display name for the entity, from the `friendly_name` attribute + /// The display name for the entity, from the `friendly_name` attribute. + /// + /// - Warning: The `friendly_name` state attribute should no longer be used to display an + /// entity's name. It does not reflect the canonical display name, which is derived from the + /// entity and device registries together with any user customizations. Resolve the display + /// name from the entity registry display data provided by the `list_for_display` endpoint + /// instead. That endpoint is not currently mapped in HAKit; refer to its definition and usage + /// in `home-assistant/core` and `home-assistant/frontend`. + @available( + *, + deprecated, + message: "friendly_name should no longer be used to display an entity's name; it doesn't reflect the entity/device registry name or user customizations. Resolve the display name from the entity registry `list_for_display` endpoint instead (not yet mapped in HAKit — see home-assistant/core and home-assistant/frontend)." + ) public var friendlyName: String? { self["friendly_name"] as? String } From 863515b8b77982941cab37e399137fdf692003c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pantalea=CC=83o?= <5808343+bgoncal@users.noreply.github.com> Date: Tue, 28 Jul 2026 10:58:07 -0300 Subject: [PATCH 2/4] Wrap single-line if bodies to satisfy SwiftFormat lint --- Source/Convenience/Services.swift | 8 ++- Tests/HAURLSessionWebSocketEngine.test.swift | 52 +++++++++++++++++--- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/Source/Convenience/Services.swift b/Source/Convenience/Services.swift index d569244e..9ea879d8 100644 --- a/Source/Convenience/Services.swift +++ b/Source/Convenience/Services.swift @@ -54,8 +54,12 @@ public struct HAServiceDefinition { var descriptionValue: String? = try? data.decode("description") // Treat empty strings as nil - if nameValue?.isEmpty == true { nameValue = nil } - if descriptionValue?.isEmpty == true { descriptionValue = nil } + if nameValue?.isEmpty == true { + nameValue = nil + } + if descriptionValue?.isEmpty == true { + descriptionValue = nil + } try self.init( domain: domain, diff --git a/Tests/HAURLSessionWebSocketEngine.test.swift b/Tests/HAURLSessionWebSocketEngine.test.swift index 78e877fb..f119bf18 100644 --- a/Tests/HAURLSessionWebSocketEngine.test.swift +++ b/Tests/HAURLSessionWebSocketEngine.test.swift @@ -125,9 +125,27 @@ internal class HAURLSessionWebSocketEngineTests: XCTestCase { engine.urlSession(session, webSocketTask: task, didCloseWith: .goingAway, reason: Data("bye".utf8)) engine.urlSession(session, task: task, didCompleteWithError: URLError(.timedOut)) - XCTAssertTrue(delegate.events.contains { if case .connected = $0 { return true } else { return false } }) - XCTAssertTrue(delegate.events.contains { if case .disconnected = $0 { return true } else { return false } }) - XCTAssertTrue(delegate.events.contains { if case .error = $0 { return true } else { return false } }) + XCTAssertTrue(delegate.events.contains { + if case .connected = $0 { + return true + } else { + return false + } + }) + XCTAssertTrue(delegate.events.contains { + if case .disconnected = $0 { + return true + } else { + return false + } + }) + XCTAssertTrue(delegate.events.contains { + if case .error = $0 { + return true + } else { + return false + } + }) } func testStartWritesAndStop() throws { @@ -138,7 +156,9 @@ internal class HAURLSessionWebSocketEngineTests: XCTestCase { let errored = expectation(description: "read fails once the socket is cancelled") errored.assertForOverFulfill = false delegate.onEvent = { event in - if case .error = event { errored.fulfill() } + if case .error = event { + errored.fulfill() + } } let request = try URLRequest(url: XCTUnwrap(URL(string: "wss://127.0.0.1:1/api/websocket"))) @@ -173,9 +193,27 @@ internal class HAURLSessionWebSocketEngineTests: XCTestCase { engine.handleReceiveResult(.success(.data(Data("bytes".utf8)))) engine.handleReceiveResult(.failure(URLError(.badServerResponse))) - XCTAssertTrue(delegate.events.contains { if case .text("hello") = $0 { return true } else { return false } }) - XCTAssertTrue(delegate.events.contains { if case .binary = $0 { return true } else { return false } }) - XCTAssertTrue(delegate.events.contains { if case .error = $0 { return true } else { return false } }) + XCTAssertTrue(delegate.events.contains { + if case .text("hello") = $0 { + return true + } else { + return false + } + }) + XCTAssertTrue(delegate.events.contains { + if case .binary = $0 { + return true + } else { + return false + } + }) + XCTAssertTrue(delegate.events.contains { + if case .error = $0 { + return true + } else { + return false + } + }) } // MARK: - Helpers From a83344683a7109bf7fca5822954796bb36cb58d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pantalea=CC=83o?= <5808343+bgoncal@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:07:46 -0300 Subject: [PATCH 3/4] Cover empty service description handling --- Tests/Services.test.swift | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Tests/Services.test.swift b/Tests/Services.test.swift index bd74c078..dc24d515 100644 --- a/Tests/Services.test.swift +++ b/Tests/Services.test.swift @@ -271,6 +271,11 @@ internal class CallServiceTests: XCTestCase { "description": "Only Description", "fields": {} }, + "with_empty_description": { + "name": "Has Name", + "description": "", + "fields": {} + }, "with_neither": { "fields": {} } @@ -279,7 +284,7 @@ internal class CallServiceTests: XCTestCase { """) let response = try HAResponseServices(data: data) let domain = try XCTUnwrap(response.allByDomain["test_domain"]) - XCTAssertEqual(domain.count, 4) + XCTAssertEqual(domain.count, 5) var service: HAServiceDefinition! @@ -298,6 +303,11 @@ internal class CallServiceTests: XCTestCase { XCTAssertEqual(service.name, "Only Description") XCTAssertEqual(service.description, "Only Description") + // Test with an empty description (treated as nil) + service = try XCTUnwrap(domain["with_empty_description"]) + XCTAssertEqual(service.name, "Has Name") + XCTAssertNil(service.description, "empty description should be treated as nil") + // Test with neither (name falls back to domain.service pair) service = try XCTUnwrap(domain["with_neither"]) XCTAssertEqual(service.name, "test_domain.with_neither") From e5c50e891d86d79901ebda6edc8521d19d8e8d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Pantalea=CC=83o?= <5808343+bgoncal@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:13:30 -0300 Subject: [PATCH 4/4] Clarify friendlyName doc comment --- Source/Data/HAEntity.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Data/HAEntity.swift b/Source/Data/HAEntity.swift index c53f61d5..5feb131f 100644 --- a/Source/Data/HAEntity.swift +++ b/Source/Data/HAEntity.swift @@ -115,7 +115,7 @@ public struct HAEntityAttributes { /// This contains all keys and values received, including those not parsed or handled otherwise public var dictionary: [String: Any] - /// The display name for the entity, from the `friendly_name` attribute. + /// The value of the entity's legacy `friendly_name` state attribute. /// /// - Warning: The `friendly_name` state attribute should no longer be used to display an /// entity's name. It does not reflect the canonical display name, which is derived from the