mirror of
https://github.com/home-assistant/iOS.git
synced 2026-04-11 20:27:50 -05:00
<!-- 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 --> Introduce keyboard handling for WKWebView: add WebViewKeyboardAvoidance helpers for animation metrics and overlap calculation, a WKWebView extension to run a JS command that scrolls the focused editable element into view, and a managed bottom constraint for web views so the view is animated above the keyboard. Wire up keyboard observers and DispatchWorkItem scheduling/cleanup in WebViewController and OnboardingAuthLoginViewController, and add the scrollFocusedElementIntoView JavaScript to WebViewJavascriptCommands. Add unit tests verifying the bottom constraint creation and JS contents. ## 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. -->
101 lines
4.1 KiB
Swift
101 lines
4.1 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)
|
|
}
|
|
|
|
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),
|
|
]
|
|
)
|
|
}
|
|
}
|