Files
iOS/Tests/App/WebView/WebViewControllerTests.swift
Bruno Pantaleão Gonçalves 0bb2d6296f Fix Safe area empty state (#4572)
<!-- 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 -->

## 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. -->
2026-04-28 10:30:52 +00:00

175 lines
6.4 KiB
Swift

@testable import HomeAssistant
import UIKit
import WebKit
import XCTest
final class WebViewControllerTests: XCTestCase {
func testMakeWebViewConfigurationRequiresUserActionForAudioPlayback() {
let config = WebViewController.makeWebViewConfiguration()
XCTAssertTrue(config.allowsInlineMediaPlayback)
XCTAssertEqual(config.mediaTypesRequiringUserActionForPlayback, .audio)
}
func testMakeWebViewBottomConstraintPinsWebViewToContainerBottom() {
let containerView = UIView()
let webView = WKWebView(frame: .zero, configuration: WebViewController.makeWebViewConfiguration())
let constraint = WebViewController.makeWebViewBottomConstraint(for: webView, in: containerView)
XCTAssertIdentical(constraint.firstItem as? WKWebView, webView)
XCTAssertEqual(constraint.firstAttribute, .bottom)
XCTAssertEqual(constraint.relation, .equal)
XCTAssertIdentical(constraint.secondItem as? UIView, containerView)
XCTAssertEqual(constraint.secondAttribute, .bottom)
}
func testUpdateWebViewBottomConstraintUsesKeyboardOverlapHeight() {
let sut = makeSUT()
let notification = keyboardNotification(frame: CGRect(x: 0, y: 424, width: 320, height: 216))
sut.updateWebViewBottomConstraint(using: notification)
XCTAssertEqual(sut.webViewBottomConstraint?.constant, -216)
}
func testScheduleFocusedElementScrollReschedulesExistingWorkItemForVisibleKeyboard() {
let sut = makeSUT()
let existingWorkItem = DispatchWorkItem {}
sut.keyboardFocusedElementScrollWorkItem = existingWorkItem
let notification = keyboardNotification(frame: CGRect(x: 0, y: 424, width: 320, height: 216), duration: 1)
sut.scheduleFocusedElementScroll(using: notification)
XCTAssertTrue(existingWorkItem.isCancelled)
guard let rescheduledWorkItem = sut.keyboardFocusedElementScrollWorkItem else {
return XCTFail("Expected a new work item to be scheduled")
}
XCTAssertFalse(rescheduledWorkItem === existingWorkItem)
}
func testScheduleFocusedElementScrollClearsWorkItemWhenKeyboardIsHidden() {
let sut = makeSUT()
let existingWorkItem = DispatchWorkItem {}
sut.keyboardFocusedElementScrollWorkItem = existingWorkItem
let notification = keyboardNotification(frame: CGRect(x: 0, y: 640, width: 320, height: 216))
sut.scheduleFocusedElementScroll(using: notification)
XCTAssertTrue(existingWorkItem.isCancelled)
XCTAssertNil(sut.keyboardFocusedElementScrollWorkItem)
}
func testEmptyStateStyleUsesUnauthenticatedVariantForAuthInvalidConnectionState() {
let sut = makeSUT()
let style = sut.emptyStateStyle(for: .authInvalid)
XCTAssertEqual(style, .unauthenticated)
}
func testEmptyStateStyleUsesDisconnectedVariantForDisconnectedConnectionState() {
let sut = makeSUT()
let style = sut.emptyStateStyle(for: .disconnected)
XCTAssertEqual(style, .disconnected)
}
func testResetEmptyStateTimerKeepsAuthInvalidConnectionState() {
let sut = makeSUT()
sut.connectionState = .authInvalid
sut.isConnected = false
sut.resetEmptyStateTimerWithLatestConnectedState()
XCTAssertEqual(sut.connectionState, .authInvalid)
}
func testUpdateFrontendConnectionStateDoesNotDowngradeAuthInvalidToDisconnected() {
let sut = makeSUT()
sut.connectionState = .authInvalid
sut.updateFrontendConnectionState(state: FrontEndConnectionState.disconnected.rawValue)
XCTAssertEqual(sut.connectionState, .authInvalid)
XCTAssertNil(sut.emptyStateTimer)
}
func testUpdateFrontendConnectionStateSchedulesTimerForDisconnectedState() {
let sut = makeSUT()
sut.updateFrontendConnectionState(state: FrontEndConnectionState.disconnected.rawValue)
XCTAssertEqual(sut.connectionState, .disconnected)
XCTAssertNotNil(sut.emptyStateTimer)
}
func testShowEmptyStateShowsErrorDetailsButtonWhenLatestLoadErrorExists() {
let sut = makeSUT()
sut.setupEmptyState()
sut.connectionState = .disconnected
sut.latestLoadError = URLError(.notConnectedToInternet)
sut.showEmptyState()
XCTAssertEqual(sut.emptyStateView?.style, .disconnected)
XCTAssertEqual(sut.emptyStateView?.showsErrorDetailsButton, true)
}
func testSetupEmptyStateAddsHostingControllerAsChild() {
let sut = makeSUT()
sut.setupEmptyState()
XCTAssertIdentical(sut.emptyStateView?.hostingViewController.parent, sut)
}
func testUpdateFrontendConnectionStateClearsLatestLoadError() {
let sut = makeSUT()
sut.latestLoadError = URLError(.timedOut)
sut.updateFrontendConnectionState(state: FrontEndConnectionState.connected.rawValue)
XCTAssertNil(sut.latestLoadError)
}
private func makeSUT() -> WebViewController {
let sut = WebViewController(server: .fake())
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 640))
sut.setValue(containerView, forKey: "view")
let webView = WKWebView(frame: .zero, configuration: WebViewController.makeWebViewConfiguration())
webView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(webView)
let bottomConstraint = WebViewController.makeWebViewBottomConstraint(for: webView, in: containerView)
sut.webView = webView
sut.webViewBottomConstraint = bottomConstraint
NSLayoutConstraint.activate([
webView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
webView.topAnchor.constraint(equalTo: containerView.topAnchor),
bottomConstraint,
])
return sut
}
private func keyboardNotification(
frame: CGRect,
duration: TimeInterval = 0.25,
curve: UIView.AnimationCurve = .easeInOut
) -> Notification {
Notification(
name: UIResponder.keyboardWillChangeFrameNotification,
object: nil,
userInfo: [
UIResponder.keyboardFrameEndUserInfoKey: frame,
UIResponder.keyboardAnimationDurationUserInfoKey: NSNumber(value: duration),
UIResponder.keyboardAnimationCurveUserInfoKey: NSNumber(value: curve.rawValue),
]
)
}
}