Skip to content

Commit

Permalink
Add a clearAll method
Browse files Browse the repository at this point in the history
  • Loading branch information
ichiho-ojima committed Jun 14, 2024
1 parent 96910e4 commit ca6d896
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 111 deletions.
2 changes: 1 addition & 1 deletion Examples/Examples/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct ContentView: View {
.labelStyle(.iconOnly)
}
Button {
proxy.clearHistory()
proxy.clearAll()
proxy.load(request: viewState.request)
} label: {
Label("Clear", systemImage: "clear")
Expand Down
6 changes: 6 additions & 0 deletions Examples/ExamplesUITests/ExamplesUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ final class ExamplesUITests: XCTestCase {
XCTAssertTrue(app.webViews.staticTexts["0"].waitForExistence(timeout: 3))
}

XCTContext.runActivity(named: "WebViewProxy.clearAll()") { _ in
app.buttons["Clear"].tap()
XCTAssertFalse(app.buttons["Go Back"].isEnabled)
XCTAssertFalse(app.buttons["Go Forward"].isEnabled)
}

XCTContext.runActivity(named: "WebView.uiDelegate(_:)") { _ in
app.webViews.buttons["Confirm"].tap()
XCTAssertTrue(app.alerts.staticTexts["Confirm Test"].waitForExistence(timeout: 3))
Expand Down
39 changes: 0 additions & 39 deletions Sources/WebUI/EnhancedWKWebViewWrapper.swift

This file was deleted.

48 changes: 48 additions & 0 deletions Sources/WebUI/Remakeable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#if os(iOS)
import UIKit
typealias OSView = UIView
#elseif os(macOS)
import AppKit
typealias OSView = NSView
#endif

final class Remakeable<Content: OSView>: OSView {
private(set) var wrappedValue: Content {
didSet {
action?(wrappedValue)
}
}
private let content: () -> Content
private var action: ((Content) -> Void)?

init(content: @escaping () -> Content) {
self.content = content
wrappedValue = content()
super.init(frame: .zero)
addSubview(wrappedValue)
setConstraints()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func remake() {
wrappedValue.removeFromSuperview()
wrappedValue = content()
addSubview(wrappedValue)
setConstraints()
}

func onRemake(perform action: @escaping (Content) -> Void) {
self.action = action
}

private func setConstraints() {
wrappedValue.translatesAutoresizingMaskIntoConstraints = false
wrappedValue.topAnchor.constraint(equalTo: topAnchor).isActive = true
wrappedValue.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
wrappedValue.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
wrappedValue.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
}
}
8 changes: 4 additions & 4 deletions Sources/WebUI/SetUpWebViewProxyAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import SwiftUI
import WebKit

struct SetUpWebViewProxyAction {
let action: @MainActor @Sendable (WKWebView, @escaping @MainActor @Sendable () -> WKWebView) -> Void
let action: @MainActor @Sendable (Remakeable<EnhancedWKWebView>) -> Void

@MainActor
func callAsFunction(_ webView: WKWebView, _ remakeHandler: @escaping @MainActor @Sendable () -> WKWebView) {
action(webView, remakeHandler)
func callAsFunction(_ webView: Remakeable<EnhancedWKWebView>) {
action(webView)
}
}

private struct SetUpWebViewProxyActionKey: EnvironmentKey {
static let defaultValue = SetUpWebViewProxyAction(action: { _, _ in })
static let defaultValue = SetUpWebViewProxyAction(action: { _ in })
}

extension EnvironmentValues {
Expand Down
26 changes: 13 additions & 13 deletions Sources/WebUI/WebView+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,35 @@ extension WebView: View {
let parent: WebView

@MainActor
private func makeView() -> EnhancedWKWebViewWrapper {
let view = EnhancedWKWebViewWrapper(configuration: parent.configuration)
setUpWebViewProxy(view.webView) {
view.remakeWebView(configuration: parent.configuration)
private func makeView() -> Remakeable<EnhancedWKWebView> {
let webView = Remakeable {
EnhancedWKWebView(frame: .zero, configuration: parent.configuration)
}
parent.applyModifiers(to: view.webView)
parent.loadInitialRequest(in: view.webView)
return view
setUpWebViewProxy(webView)
parent.applyModifiers(to: webView.wrappedValue)
parent.loadInitialRequest(in: webView.wrappedValue)
return webView
}

@MainActor
private func updateView(_ view: EnhancedWKWebViewWrapper) {
parent.applyModifiers(to: view.webView)
private func updateView(_ view: Remakeable<EnhancedWKWebView>) {
parent.applyModifiers(to: view.wrappedValue)
}

#if os(iOS)
func makeUIView(context: Context) -> EnhancedWKWebViewWrapper {
func makeUIView(context: Context) -> Remakeable<EnhancedWKWebView> {
makeView()
}

func updateUIView(_ view: EnhancedWKWebViewWrapper, context: Context) {
func updateUIView(_ view: Remakeable<EnhancedWKWebView>, context: Context) {
updateView(view)
}
#elseif os(macOS)
func makeNSView(context: Context) -> EnhancedWKWebViewWrapper {
func makeNSView(context: Context) -> Remakeable<EnhancedWKWebView> {
makeView()
}

func updateNSView(_ view: EnhancedWKWebViewWrapper, context: Context) {
func updateNSView(_ view: Remakeable<EnhancedWKWebView>, context: Context) {
updateView(view)
}
#endif
Expand Down
41 changes: 19 additions & 22 deletions Sources/WebUI/WebViewProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import WebKit
@available(iOS 16.4, macOS 13.3, *)
@MainActor
public final class WebViewProxy: ObservableObject {
private weak var webView: WKWebView?
private var remakeHandler: (() -> WKWebView)?
private(set) weak var webView: Remakeable<EnhancedWKWebView>?

/// The page title.
@Published public private(set) var title: String?
Expand Down Expand Up @@ -38,14 +37,18 @@ public final class WebViewProxy: ObservableObject {
task?.cancel()
}

func setUp(_ webView: WKWebView, _ remakeHandler: @escaping () -> WKWebView) {
setUpWebView(webView)
setUpRemakeHandler(remakeHandler)
}

func setUpWebView(_ webView: WKWebView) {
func setUp(_ webView: Remakeable<EnhancedWKWebView>) {
self.webView = webView
observe(webView.wrappedValue)

webView.onRemake { [weak self] in
guard let self else { return }
observe($0)
}
}

private func observe(_ webView: WKWebView) {
task?.cancel()
task = Task {
await withTaskGroup(of: Void.self) { group in
group.addTask { @MainActor in
Expand Down Expand Up @@ -87,30 +90,26 @@ public final class WebViewProxy: ObservableObject {
}
}

func setUpRemakeHandler(_ remakeHandler: @escaping () -> WKWebView) {
self.remakeHandler = remakeHandler
}

/// Navigates to a requested URL.
/// - Parameters:
/// - request: The request specifying the URL to which to navigate.
public func load(request: URLRequest) {
webView?.load(request)
webView?.wrappedValue.load(request)
}

/// Reloads the current webpage.
public func reload() {
webView?.reload()
webView?.wrappedValue.reload()
}

/// Navigates to the back item in the back-forward list.
public func goBack() {
webView?.goBack()
webView?.wrappedValue.goBack()
}

/// Navigates to the forward item in the back-forward list.
public func goForward() {
webView?.goForward()
webView?.wrappedValue.goForward()
}

/// Evaluates the specified JavaScript string.
Expand Down Expand Up @@ -140,7 +139,7 @@ public final class WebViewProxy: ObservableObject {
public func evaluateJavaScript(_ javaScriptString: String) async throws -> Any? {
guard let webView else { return nil }
return try await withCheckedThrowingContinuation { continuation in
webView.evaluateJavaScript(javaScriptString) { result, error in
webView.wrappedValue.evaluateJavaScript(javaScriptString) { result, error in
if let error {
continuation.resume(throwing: error)
} else {
Expand All @@ -150,12 +149,10 @@ public final class WebViewProxy: ObservableObject {
}
}

/// Clears all history.
/// Clears history and cookies.
///
/// As a side effect, the WKWebView instance will be remade.
public func clearHistory() {
guard let webView = remakeHandler?() else { return }
task?.cancel()
setUpWebView(webView)
public func clearAll() {
webView?.remake()
}
}
2 changes: 1 addition & 1 deletion Sources/WebUI/WebViewReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public struct WebViewReader<Content: View>: View {
public var body: some View {
content(proxy)
.environment(\.setUpWebViewProxy, SetUpWebViewProxyAction(action: {
proxy.setUp($0, $1)
proxy.setUp($0)
}))
}
}
2 changes: 1 addition & 1 deletion Tests/WebUITests/Mock.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@testable import WebUI
import WebKit

final class WKWebViewMock: EnhancedWKWebView {
final class EnhancedWKWebViewMock: EnhancedWKWebView {
private(set) var loadedRequest: URLRequest?
private(set) var reloadCalled = false
private(set) var goBackCalled = false
Expand Down
58 changes: 36 additions & 22 deletions Tests/WebUITests/WebViewProxyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,60 +5,74 @@ final class WebViewProxyTests: XCTestCase {
@MainActor
func test_load_the_specified_URLRequest() {
let sut = WebViewProxy()
let webViewMock = WKWebViewMock()
sut.setUpWebView(webViewMock)
let webViewMock = Remakeable {
EnhancedWKWebViewMock() as EnhancedWKWebView
}
sut.setUp(webViewMock)
let request = URLRequest(url: URL(string: "https://www.example.com")!)
sut.load(request: request)
XCTAssertEqual(webViewMock.loadedRequest, request)
XCTAssertEqual((webViewMock.wrappedValue as! EnhancedWKWebViewMock).loadedRequest, request)
}

@MainActor
func test_reload() {
let sut = WebViewProxy()
let webViewMock = WKWebViewMock()
sut.setUpWebView(webViewMock)
let webViewMock = Remakeable {
EnhancedWKWebViewMock() as EnhancedWKWebView
}
sut.setUp(webViewMock)
sut.reload()
XCTAssertTrue(webViewMock.reloadCalled)
XCTAssertTrue((webViewMock.wrappedValue as! EnhancedWKWebViewMock).reloadCalled)
}

@MainActor
func test_go_back() {
let sut = WebViewProxy()
let webViewMock = WKWebViewMock()
sut.setUpWebView(webViewMock)
let webViewMock = Remakeable {
EnhancedWKWebViewMock() as EnhancedWKWebView
}
sut.setUp(webViewMock)
sut.goBack()
XCTAssertTrue(webViewMock.goBackCalled)
XCTAssertTrue((webViewMock.wrappedValue as! EnhancedWKWebViewMock).goBackCalled)
}

@MainActor
func test_go_forward() {
let sut = WebViewProxy()
let webViewMock = WKWebViewMock()
sut.setUpWebView(webViewMock)
let webViewMock = Remakeable {
EnhancedWKWebViewMock() as EnhancedWKWebView
}
sut.setUp(webViewMock)
sut.goForward()
XCTAssertTrue(webViewMock.goForwardCalled)
XCTAssertTrue((webViewMock.wrappedValue as! EnhancedWKWebViewMock).goForwardCalled)
}

@MainActor
func test_evaluate_JavaScript() async throws {
let sut = WebViewProxy()
let webViewMock = WKWebViewMock()
sut.setUpWebView(webViewMock)
let webViewMock = Remakeable {
EnhancedWKWebViewMock() as EnhancedWKWebView
}
sut.setUp(webViewMock)
let actual = try await sut.evaluateJavaScript("test")
XCTAssertEqual(webViewMock.javaScriptString, "test")
XCTAssertEqual((webViewMock.wrappedValue as! EnhancedWKWebViewMock).javaScriptString, "test")
let result = try XCTUnwrap(actual as? Bool)
XCTAssertTrue(result)
}

@MainActor
func test_clear_history() async {
func test_clear_all() async {
let sut = WebViewProxy()
let expectation = XCTestExpectation()
sut.setUpRemakeHandler {
expectation.fulfill()
return WKWebViewMock()
let webViewMock = Remakeable {
EnhancedWKWebViewMock() as EnhancedWKWebView
}
sut.clearHistory()
await fulfillment(of: [expectation], timeout: 0.1)
sut.setUp(webViewMock)
let oldInstance = sut.webView?.wrappedValue

sut.clearAll()

let newInstance = sut.webView?.wrappedValue

XCTAssertNotEqual(oldInstance, newInstance)
}
}
Loading

0 comments on commit ca6d896

Please sign in to comment.