Files
iOS/Tests/App/WebView/WebViewExternalMessageHandlerTests.swift
Bruno Pantaleão Gonçalves 6e84ff4cb6 Migrate app from UIKit based app to SwiftUI (#4748)
<!-- Thank you for submitting a Pull Request and helping to improve Home
Assistant. Please complete the following sections to help the processing
and review of your changes. Please do not delete anything from this
template. -->

## Summary
<!-- Provide a brief summary of the changes you have made and most
importantly what they aim to achieve -->
This PR is a massive refactor of how the app handles UI presentation and
navigation, goin from the UIKit based apps style to SwiftUI.

## Screenshots
<!-- If this is a user-facing change not in the frontend, please include
screenshots in light and dark mode. -->

## Link to pull request in Documentation repository
<!-- Pull requests that add, change or remove functionality must have a
corresponding pull request in the Companion App Documentation repository
(https://github.com/home-assistant/companion.home-assistant). Please add
the number of this pull request after the "#" -->
Documentation: home-assistant/companion.home-assistant#

## Any other notes
<!-- If there is any other information of note, like if this Pull
Request is part of a bigger change, please include it here. -->

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 10:33:17 +02:00

262 lines
9.1 KiB
Swift

@testable import HomeAssistant
import Improv_iOS
import PromiseKit
@testable import Shared
import SwiftUI
import XCTest
final class WebViewExternalMessageHandlerTests: XCTestCase {
private var sut: WebViewExternalMessageHandler!
private var mockWebViewController: MockWebViewController!
private var originalMatterCommission: ((Server) -> Promise<String?>)!
override func setUp() async throws {
originalMatterCommission = Current.matter.commission
mockWebViewController = MockWebViewController()
sut = WebViewExternalMessageHandler(
improvManager: ImprovManager.shared
)
sut.webViewController = mockWebViewController
}
override func tearDown() async throws {
Current.matter.commission = originalMatterCommission
originalMatterCommission = nil
sut = nil
mockWebViewController = nil
}
@MainActor func testHandleExternalMessageConfigScreenShowShowSettings() {
let coordinator = MockAppCoordinator()
let settingsShown = expectation(description: "showSettings called")
coordinator.onShowSettings = { settingsShown.fulfill() }
Current.sceneManager.registerAppCoordinator(coordinator)
let dictionary: [String: Any] = [
"id": 1,
"message": "",
"command": "",
"type": "config_screen/show",
]
sut.handleExternalMessage(dictionary)
wait(for: [settingsShown], timeout: 1)
XCTAssertTrue(coordinator.showSettingsCalled)
}
@MainActor func testHandleExternalMessageThemeUpdateNotifyThemeColors() {
let dictionary: [String: Any] = [
"id": 1,
"message": "",
"command": "",
"type": "theme-update",
]
sut.handleExternalMessage(dictionary)
XCTAssertEqual(mockWebViewController.lastEvaluatedJavaScriptScript, "notifyThemeColors()")
}
@MainActor func testHandleExternalMessageBarCodeScanPresentsScanner() {
let dictionary: [String: Any] = [
"id": 1,
"message": "",
"command": "",
"type": "bar_code/scan",
"payload": [
"title": "abc",
"description": "abc2",
],
]
sut.handleExternalMessage(dictionary)
XCTAssertTrue(mockWebViewController.overlayedController is BarcodeScannerHostingController)
}
@MainActor func testHandleExternalMessageBarCodeCloseClosesScanner() {
let dictionary: [String: Any] = [
"id": 1,
"message": "",
"command": "",
"type": "bar_code/scan",
"payload": [
"title": "abc",
"description": "abc2",
],
]
// Open scanner
sut.handleExternalMessage(dictionary)
let dictionary2: [String: Any] = [
"id": 2,
"message": "",
"command": "",
"type": "bar_code/close",
]
// Close scanner
sut.handleExternalMessage(dictionary2)
XCTAssertTrue(mockWebViewController.dismissOverlayControllerCalled)
XCTAssertTrue(mockWebViewController.dismissControllerAboveOverlayControllerCalled)
}
@MainActor func testHandleExternalMessageBarCodeNotifyNotifies() {
let dictionary: [String: Any] = [
"id": 1,
"message": "",
"command": "",
"type": "bar_code/scan",
"payload": [
"title": "abc",
"description": "abc2",
],
]
// Open scanner
sut.handleExternalMessage(dictionary)
let dictionary2: [String: Any] = [
"id": 1,
"message": "",
"command": "",
"type": "bar_code/notify",
"payload": [
"message": "abc",
],
]
sut.handleExternalMessage(dictionary2)
XCTAssertEqual(mockWebViewController.shownBannerRequests.last?.id, "BarcodeScannerMessage")
XCTAssertEqual(mockWebViewController.shownBannerRequests.last?.message, "abc")
}
@MainActor func testHandleExternalMessageStoreInPlatformKeychainOpenTransferFlow() {
let dictionary: [String: Any] = [
"id": 1,
"message": "",
"command": "",
"type": "thread/store_in_platform_keychain",
"payload": [
"mac_extended_address": "abc",
"active_operational_dataset": "abc2",
],
]
sut.handleExternalMessage(dictionary)
XCTAssertTrue(
mockWebViewController
.overlayedController is UIHostingController<
ThreadCredentialsSharingView<ThreadTransferCredentialToKeychainViewModel>
>
)
XCTAssertEqual(mockWebViewController.overlayedController?.modalTransitionStyle, .crossDissolve)
XCTAssertEqual(mockWebViewController.overlayedController?.modalPresentationStyle, .overFullScreen)
XCTAssertEqual(mockWebViewController.overlayedController?.view.backgroundColor, .clear)
}
@MainActor func testHandleExternalMessageImportThreadCredentialsStartImportFlow() {
let dictionary: [String: Any] = [
"id": 1,
"message": "",
"command": "",
"type": "thread/import_credentials",
]
sut.handleExternalMessage(dictionary)
XCTAssertTrue(
mockWebViewController
.overlayedController is UIHostingController<
ThreadCredentialsSharingView<ThreadTransferCredentialToHAViewModel>
>
)
XCTAssertEqual(mockWebViewController.overlayedController?.modalTransitionStyle, .crossDissolve)
XCTAssertEqual(mockWebViewController.overlayedController?.modalPresentationStyle, .overFullScreen)
XCTAssertEqual(mockWebViewController.overlayedController?.view.backgroundColor, .clear)
}
@MainActor func testHandleExternalMessageMatterCommissionSendsFinishMessageWithDeviceName() throws {
let deviceName = "Kitchen Plug"
let expectation = expectation(description: "Matter commission finish message sent")
mockWebViewController.evaluateJavaScriptExpectation = expectation
Current.matter.commission = { _ in .value(deviceName) }
let dictionary: [String: Any] = [
"id": 1,
"message": "",
"command": "",
"type": "matter/commission",
]
sut.handleExternalMessage(dictionary)
wait(for: [expectation], timeout: 1)
let script = try XCTUnwrap(mockWebViewController.lastEvaluatedJavaScriptScript)
let message = try externalBusMessage(from: script)
let payload = try XCTUnwrap(message["payload"] as? [String: Any])
XCTAssertEqual(message["type"] as? String, "command")
XCTAssertEqual(message["command"] as? String, WebViewExternalBusOutgoingMessage.matterCommissionFinish.rawValue)
XCTAssertEqual(payload["name"] as? String, deviceName)
}
@MainActor func testHandleExternalMessageShowAssistShowsAssist() {
let dictionary: [String: Any] = [
"id": 1,
"message": "",
"command": "",
"type": "assist/show",
]
sut.handleExternalMessage(dictionary)
XCTAssertTrue(mockWebViewController.overlayedController is UIHostingController<AssistView>)
}
@MainActor func testHandleExternalMessageOpenVoiceDeviceSettingsShowsSettings() {
let coordinator = MockAppCoordinator()
let assistSettingsShown = expectation(description: "showAssistSettings called")
coordinator.onShowAssistSettings = { assistSettingsShown.fulfill() }
Current.sceneManager.registerAppCoordinator(coordinator)
let dictionary: [String: Any] = [
"id": 1,
"message": "",
"command": "",
"type": "assist/settings",
]
sut.handleExternalMessage(dictionary)
wait(for: [assistSettingsShown], timeout: 1)
XCTAssertTrue(coordinator.showAssistSettingsCalled)
}
private func externalBusMessage(from script: String) throws -> [String: Any] {
let prefix = "window.externalBus("
XCTAssertTrue(script.hasPrefix(prefix))
XCTAssertTrue(script.hasSuffix(")"))
let jsonString = String(script.dropFirst(prefix.count).dropLast())
let jsonObject = try JSONSerialization.jsonObject(with: Data(jsonString.utf8))
return try XCTUnwrap(jsonObject as? [String: Any])
}
@MainActor func testHandleExternalMessageCameraPlayerShowPresentsCameraPlayer() {
let dictionary: [String: Any] = [
"id": 1,
"message": "",
"command": "",
"type": "camera/show",
"payload": [
"entity_id": "camera.front_door",
"camera_name": "Front Door",
],
]
sut.handleExternalMessage(dictionary)
XCTAssertNotNil(mockWebViewController.overlayedController)
XCTAssertEqual(mockWebViewController.overlayedController?.modalPresentationStyle, .overFullScreen)
}
}