feat(KAN-70): integrate native workspace and update settings
CI/CD / preconditions (pull_request) Successful in 1s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 1m2s
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 1m38s
CI/CD / test_and_check_app (pull_request) Successful in 6m47s
CI/CD / Publish immutable image (pull_request) Skipped
CI/CD / preconditions (pull_request) Successful in 1s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 1m2s
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 1m38s
CI/CD / test_and_check_app (pull_request) Successful in 6m47s
CI/CD / Publish immutable image (pull_request) Skipped
This commit is contained in:
@@ -135,6 +135,83 @@ final class WorkspaceTests: XCTestCase {
|
||||
"Changing workspaces must retain native gesture navigation")
|
||||
}
|
||||
|
||||
func testSettingsProtocolRejectsUnsupportedActionsAndInvalidPayloads() throws {
|
||||
for body in [
|
||||
#"{"version":2,"id":1,"action":"getState","payload":{}}"#,
|
||||
#"{"version":true,"id":1,"action":"getState","payload":{}}"#,
|
||||
#"{"version":1,"id":1,"action":"installUpdate","payload":{"version":"1"}}"#,
|
||||
#"{"version":1,"id":1,"action":"setWorkspace","payload":{"server":12}}"#,
|
||||
#"{"version":1,"id":1,"action":"getState","payload":{"extra":1}}"#,
|
||||
#"{"version":1,"id":1,"action":"shell","payload":{}}"#,
|
||||
] { XCTAssertThrowsError(try NativeSettingsRequest.parse(body), body) }
|
||||
XCTAssertThrowsError(try NativeSettingsRequest.parse(String(repeating: "x", count: 8193)))
|
||||
XCTAssertEqual(try NativeSettingsRequest.parse(#"{"version":1,"id":2,"action":"getState","payload":{}}"#).id, 2)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testSettingsBridgeHidesToolbarOnlyAfterHandshakeAndRejectsFramesAndOldWorkspaces() async throws {
|
||||
let name = "KAN70-settings-\(UUID())"
|
||||
let defaults = try XCTUnwrap(UserDefaults(suiteName: name))
|
||||
defer { defaults.removePersistentDomain(forName: name) }
|
||||
let controller = WorkspaceViewController(defaults: defaults)
|
||||
controller.loadViewIfNeeded()
|
||||
controller.view.frame = CGRect(x: 0, y: 0, width: 390, height: 844)
|
||||
let url = URL(string: "https://example.invalid/")!
|
||||
controller.open(url)
|
||||
let originalProfile = controller.server!.profile
|
||||
let webview = try XCTUnwrap(controller.workspace)
|
||||
webview.stopLoading()
|
||||
webview.loadSimulatedRequest(URLRequest(url: url.appendingPathComponent("login")), responseHTML: "<html><body>Login<iframe srcdoc='<p>frame</p>'></iframe></body></html>")
|
||||
try await waitFor(webview, "document.readyState === 'complete' && typeof window.kanbanNative === 'object'")
|
||||
let state = try await callScript(webview, "return await window.kanbanNative.request('getState')") as? [String: Any]
|
||||
XCTAssertEqual(state?["platform"] as? String, "ios")
|
||||
XCTAssertEqual(state?["workspace"] as? String, url.absoluteString)
|
||||
XCTAssertEqual(state?["capabilities"] as? [String], ["workspace"])
|
||||
XCTAssertNil(state?["updates"])
|
||||
let toolbar = try XCTUnwrap(controller.view.subviews.compactMap { $0 as? UIToolbar }.first)
|
||||
XCTAssertFalse(toolbar.isHidden, "Older pages must retain native recovery")
|
||||
let rejected = try await callScript(webview, """
|
||||
try {
|
||||
await window.frames[0].webkit.messageHandlers.kanbanSettings.postMessage(JSON.stringify({version:1,id:2,action:'settingsReady',payload:{}}));
|
||||
return false;
|
||||
} catch { return true; }
|
||||
""")
|
||||
XCTAssertEqual(rejected as? Bool, true)
|
||||
XCTAssertFalse(toolbar.isHidden)
|
||||
_ = try await callScript(webview, "return await window.kanbanNative.request('settingsReady')")
|
||||
controller.view.layoutIfNeeded()
|
||||
XCTAssertTrue(toolbar.isHidden)
|
||||
XCTAssertEqual(toolbar.frame.height, 0)
|
||||
XCTAssertEqual(webview.frame.minY, controller.view.safeAreaLayoutGuide.layoutFrame.minY, accuracy: 1)
|
||||
let invalid = try await callScript(webview, "try { await window.kanbanNative.request('setWorkspace', {server:'http://bad.test'}); return false; } catch { return true; }")
|
||||
XCTAssertEqual(invalid as? Bool, true)
|
||||
XCTAssertEqual(controller.server?.url, url)
|
||||
_ = try await callScript(webview, "return await window.kanbanNative.request('openSettings')")
|
||||
XCTAssertFalse(controller.view.subviews.first { $0.accessibilityIdentifier == "serverSettings" }!.isHidden)
|
||||
_ = try await callScript(webview, "return await window.kanbanNative.request('setWorkspace', {server:'https://other.invalid/app/'})")
|
||||
for _ in 0..<50 {
|
||||
if controller.workspace !== webview { break }
|
||||
try await Task.sleep(for: .milliseconds(20))
|
||||
}
|
||||
XCTAssertFalse(controller.workspace === webview)
|
||||
XCTAssertNotEqual(controller.server?.profile, originalProfile)
|
||||
let restored = WorkspaceViewController(defaults: defaults)
|
||||
restored.loadViewIfNeeded()
|
||||
XCTAssertEqual(restored.server?.url.absoluteString, "https://other.invalid/app/")
|
||||
let stale = try await callScript(webview, "try { await window.kanbanNative.request('setWorkspace', {server:'https://stale.invalid/'}); return false; } catch { return true; }")
|
||||
XCTAssertEqual(stale as? Bool, true)
|
||||
XCTAssertEqual(controller.server?.url.absoluteString, "https://other.invalid/app/")
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func callScript(_ webview: WKWebView, _ script: String) async throws -> Any {
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
webview.callAsyncJavaScript(script, arguments: [:], in: nil, in: .page) { result in
|
||||
continuation.resume(with: result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func waitFor(_ webview: WKWebView, _ expression: String) async throws {
|
||||
for _ in 0..<100 {
|
||||
@@ -201,7 +278,7 @@ final class WorkspaceTests: XCTestCase {
|
||||
controller.open(url)
|
||||
let server = try XCTUnwrap(controller.server)
|
||||
XCTAssertEqual(controller.workspace?.configuration.websiteDataStore.identifier, server.profile)
|
||||
XCTAssertEqual(controller.workspace?.configuration.userContentController.userScripts.count, 1)
|
||||
XCTAssertEqual(controller.workspace?.configuration.userContentController.userScripts.count, 2)
|
||||
let restored = WorkspaceViewController(defaults: defaults)
|
||||
restored.loadViewIfNeeded()
|
||||
XCTAssertEqual(restored.server, server)
|
||||
|
||||
Reference in New Issue
Block a user