diff --git a/Examples/Examples.xcodeproj/project.pbxproj b/Examples/Examples.xcodeproj/project.pbxproj index 167c135..80af2b2 100644 --- a/Examples/Examples.xcodeproj/project.pbxproj +++ b/Examples/Examples.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 183D25982B566967001FDBDB /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 183D25972B566967001FDBDB /* Preview Assets.xcassets */; }; 183D25A12B5669ED001FDBDB /* WebUI in Frameworks */ = {isa = PBXBuildFile; productRef = 183D25A02B5669ED001FDBDB /* WebUI */; }; 183D25A32B566AE8001FDBDB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 183D25A22B566AE8001FDBDB /* AppDelegate.swift */; }; + 1852BC8F2CBFB72600133987 /* sample.html in Resources */ = {isa = PBXBuildFile; fileRef = 1852BC8E2CBFB72600133987 /* sample.html */; }; 1899E0F72BC7AE8E00BDCFD4 /* ExamplesUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1899E0F62BC7AE8E00BDCFD4 /* ExamplesUITests.swift */; }; 18C686122B6B3DB600D45A40 /* ContentViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C686112B6B3DB600D45A40 /* ContentViewState.swift */; }; /* End PBXBuildFile section */ @@ -37,6 +38,7 @@ 183D259E2B56699B001FDBDB /* WebUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = WebUI; path = ..; sourceTree = ""; }; 183D25A22B566AE8001FDBDB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 183D25A42B566BDB001FDBDB /* Examples.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = Examples.xctestplan; sourceTree = ""; }; + 1852BC8E2CBFB72600133987 /* sample.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = sample.html; sourceTree = ""; }; 1899E0F42BC7AE8E00BDCFD4 /* ExamplesUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExamplesUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 1899E0F62BC7AE8E00BDCFD4 /* ExamplesUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExamplesUITests.swift; sourceTree = ""; }; 18C686112B6B3DB600D45A40 /* ContentViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentViewState.swift; sourceTree = ""; }; @@ -92,6 +94,7 @@ 183D25932B566967001FDBDB /* Assets.xcassets */, 183D25952B566967001FDBDB /* Examples.entitlements */, 183D25962B566967001FDBDB /* Preview Content */, + 1852BC8E2CBFB72600133987 /* sample.html */, ); path = Examples; sourceTree = ""; @@ -205,6 +208,7 @@ files = ( 183D25982B566967001FDBDB /* Preview Assets.xcassets in Resources */, 183D25942B566967001FDBDB /* Assets.xcassets in Resources */, + 1852BC8F2CBFB72600133987 /* sample.html in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Examples/Examples/ContentView.swift b/Examples/Examples/ContentView.swift index 09aadbe..f38b5d5 100644 --- a/Examples/Examples/ContentView.swift +++ b/Examples/Examples/ContentView.swift @@ -41,6 +41,12 @@ struct ContentView: View { Label("Clear", systemImage: "clear") .labelStyle(.iconOnly) } + Button { + proxy.loadHTMLString(viewState.htmlString, baseURL: viewState.htmlURL) + } label: { + Label("Load HTML String", systemImage: "doc") + .labelStyle(.iconOnly) + } } .padding(.vertical, 8) diff --git a/Examples/Examples/ContentViewState.swift b/Examples/Examples/ContentViewState.swift index 2dd8e21..54a5827 100644 --- a/Examples/Examples/ContentViewState.swift +++ b/Examples/Examples/ContentViewState.swift @@ -37,8 +37,11 @@ final class ContentViewState: NSObject, ObservableObject { let configuration = WKWebViewConfiguration() let request = URLRequest(url: URL(string: "https://cybozu.github.io/webview-debugger")!) + let htmlURL = Bundle.main.url(forResource: "sample", withExtension: "html")! + let htmlString: String override init() { + htmlString = try! String(contentsOf: htmlURL, encoding: .utf8) super.init() setCookie(name: "SampleKey", value: "SampleValue") } diff --git a/Examples/Examples/sample.html b/Examples/Examples/sample.html new file mode 100644 index 0000000..b5b4ace --- /dev/null +++ b/Examples/Examples/sample.html @@ -0,0 +1,15 @@ + + + + Sample + + + + + +
+

Sample

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec mattis augue dui, ut vulputate diam pretium id.

+
+ + diff --git a/Examples/ExamplesUITests/ExamplesUITests.swift b/Examples/ExamplesUITests/ExamplesUITests.swift index ea6e81f..c5ec2b0 100644 --- a/Examples/ExamplesUITests/ExamplesUITests.swift +++ b/Examples/ExamplesUITests/ExamplesUITests.swift @@ -51,6 +51,11 @@ final class ExamplesUITests: XCTestCase { XCTAssertTrue(app.webViews.staticTexts["0"].waitForExistence(timeout: 3)) } + XCTContext.runActivity(named: "WebViewProxy.loadHTMLString()") { _ in + app.buttons["Load HTML String"].tap() + XCTAssertTrue(app.webViews.staticTexts["Sample"].waitForExistence(timeout: 15)) + } + XCTContext.runActivity(named: "WebViewProxy.clearAll()") { _ in app.buttons["Clear"].tap() XCTAssertFalse(app.buttons["Go Back"].isEnabled) diff --git a/Sources/WebUI/WebViewProxy.swift b/Sources/WebUI/WebViewProxy.swift index f3d96f9..9148554 100644 --- a/Sources/WebUI/WebViewProxy.swift +++ b/Sources/WebUI/WebViewProxy.swift @@ -92,6 +92,14 @@ public final class WebViewProxy: ObservableObject { webView?.wrappedValue.load(request) } + /// Loads the contents of the specified HTML string and navigates to it. + /// - Parameters: + /// - string: The string to use as the contents of the webpage. + /// - baseURL: The base URL to use when the system resolves relative URLs within the HTML string. + public func loadHTMLString(_ string: String, baseURL: URL?) { + webView?.wrappedValue.loadHTMLString(string, baseURL: baseURL) + } + /// Reloads the current webpage. public func reload() { webView?.wrappedValue.reload() diff --git a/Tests/WebUITests/Mock.swift b/Tests/WebUITests/Mock.swift index f2b51ba..6758fac 100644 --- a/Tests/WebUITests/Mock.swift +++ b/Tests/WebUITests/Mock.swift @@ -3,6 +3,8 @@ import WebKit final class EnhancedWKWebViewMock: EnhancedWKWebView { private(set) var loadedRequest: URLRequest? + private(set) var loadedHTMLString: String? + private(set) var loadedBaseURL: URL? private(set) var reloadCalled = false private(set) var goBackCalled = false private(set) var goForwardCalled = false @@ -13,6 +15,12 @@ final class EnhancedWKWebViewMock: EnhancedWKWebView { return nil } + override func loadHTMLString(_ string: String, baseURL: URL?) -> WKNavigation? { + loadedHTMLString = string + loadedBaseURL = baseURL + return nil + } + override func reload() -> WKNavigation? { reloadCalled = true return nil diff --git a/Tests/WebUITests/WebViewProxyTests.swift b/Tests/WebUITests/WebViewProxyTests.swift index 6735e79..4e98966 100644 --- a/Tests/WebUITests/WebViewProxyTests.swift +++ b/Tests/WebUITests/WebViewProxyTests.swift @@ -14,6 +14,18 @@ final class WebViewProxyTests: XCTestCase { XCTAssertEqual((webViewMock.wrappedValue as! EnhancedWKWebViewMock).loadedRequest, request) } + @MainActor + func test_load_html_string() { + let sut = WebViewProxy() + let webViewMock = Remakeable { + EnhancedWKWebViewMock() as EnhancedWKWebView + } + sut.setUp(webViewMock) + sut.loadHTMLString("", baseURL: URL(string: "/dummy")!) + XCTAssertEqual((webViewMock.wrappedValue as! EnhancedWKWebViewMock).loadedHTMLString, "") + XCTAssertEqual((webViewMock.wrappedValue as! EnhancedWKWebViewMock).loadedBaseURL, URL(string: "/dummy")!) + } + @MainActor func test_reload() { let sut = WebViewProxy()