diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 4ef4a4a..7531174 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -4,24 +4,23 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -on: +on: push: branches: - main pull_request: -env: - XCODEBUILD_DESTINATION_IOS: 'platform=iOS Simulator,name=iPhone 14 Pro' - jobs: build-and-test: - runs-on: macos-latest + runs-on: macos-13 steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Select Xcode - run: sudo xcode-select -switch /Applications/Xcode_14.2.app && /usr/bin/xcodebuild -version + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest-stable - name: Run Tests - run: xcodebuild test -project Strada.xcodeproj -scheme Strada -destination "${{ env.XCODEBUILD_DESTINATION_IOS }}" -resultBundlePath TestResults \ No newline at end of file + run: xcodebuild test -scheme Strada -destination "name=iPhone 15 Pro" | xcpretty && exit ${PIPESTATUS[0]} diff --git a/Source/BridgeDelegate.swift b/Source/BridgeDelegate.swift index 2743a86..643924f 100644 --- a/Source/BridgeDelegate.swift +++ b/Source/BridgeDelegate.swift @@ -75,32 +75,32 @@ public final class BridgeDelegate: BridgingDelegate { // MARK: - Destination lifecycle public func onViewDidLoad() { - logger.debug("bridgeDestinationViewDidLoad: \(self.location)") + logger.debug("bridgeDestinationViewDidLoad: \(self.resolvedLocation)") destinationIsActive = true activeComponents.forEach { $0.viewDidLoad() } } public func onViewWillAppear() { - logger.debug("bridgeDestinationViewWillAppear: \(self.location)") + logger.debug("bridgeDestinationViewWillAppear: \(self.resolvedLocation)") destinationIsActive = true activeComponents.forEach { $0.viewWillAppear() } } public func onViewDidAppear() { - logger.debug("bridgeDestinationViewDidAppear: \(self.location)") + logger.debug("bridgeDestinationViewDidAppear: \(self.resolvedLocation)") destinationIsActive = true activeComponents.forEach { $0.viewDidAppear() } } public func onViewWillDisappear() { activeComponents.forEach { $0.viewWillDisappear() } - logger.debug("bridgeDestinationViewWillDisappear: \(self.location)") + logger.debug("bridgeDestinationViewWillDisappear: \(self.resolvedLocation)") } public func onViewDidDisappear() { activeComponents.forEach { $0.viewDidDisappear() } destinationIsActive = false - logger.debug("bridgeDestinationViewDidDisappear: \(self.location)") + logger.debug("bridgeDestinationViewDidDisappear: \(self.resolvedLocation)") } // MARK: Retrieve component by type @@ -125,7 +125,7 @@ public final class BridgeDelegate: BridgingDelegate { @discardableResult public func bridgeDidReceiveMessage(_ message: Message) -> Bool { guard destinationIsActive, - location == message.metadata?.url else { + resolvedLocation == message.metadata?.url else { logger.warning("bridgeDidIgnoreMessage: \(String(describing: message))") return false } @@ -141,6 +141,9 @@ public final class BridgeDelegate: BridgingDelegate { private var initializedComponents: [String: BridgeComponent] = [:] private var destinationIsActive = false private let componentTypes: [BridgeComponent.Type] + private var resolvedLocation: String { + webView?.url?.absoluteString ?? location + } private var activeComponents: [BridgeComponent] { return initializedComponents.values.filter { _ in destinationIsActive } diff --git a/Tests/BridgeDelegateTests.swift b/Tests/BridgeDelegateTests.swift index daf0638..b27f13e 100644 --- a/Tests/BridgeDelegateTests.swift +++ b/Tests/BridgeDelegateTests.swift @@ -76,6 +76,55 @@ class BridgeDelegateTests: XCTestCase { XCTAssertFalse(delegate.bridgeDidReceiveMessage(message)) } + // Web view URL takes precedence over the provided location. + func test_bridgeHandlesRedirectedWebViewURL() { + let redirectedLocation = "https://37signals.com/sign-in" + bridge.webView = RedirectedWebView(location: redirectedLocation) + + let message = Message(id: "1", + component: "two", + event: "connect", + metadata: .init(url: redirectedLocation), + jsonData: json) + + var component: BridgeComponentSpy? = delegate.component() + + XCTAssertNil(component) + XCTAssertTrue(delegate.bridgeDidReceiveMessage(message)) + + component = delegate.component() + + XCTAssertNotNil(component) + // Make sure the component has delegate set, and did receive the message. + XCTAssertTrue(component!.onReceiveMessageWasCalled) + XCTAssertEqual(component?.onReceiveMessageArg, message) + XCTAssertNotNil(component?.delegate) + } + + // When web view URL is nil, the bride delegate falls back to the original location. + func test_bridgeFallsbackToOriginalDestination() { + bridge.webView = RedirectedWebView(location: nil) + + let message = Message(id: "1", + component: "two", + event: "connect", + metadata: .init(url: "https://37signals.com"), + jsonData: json) + + var component: BridgeComponentSpy? = delegate.component() + + XCTAssertNil(component) + XCTAssertTrue(delegate.bridgeDidReceiveMessage(message)) + + component = delegate.component() + + XCTAssertNotNil(component) + // Make sure the component has delegate set, and did receive the message. + XCTAssertTrue(component!.onReceiveMessageWasCalled) + XCTAssertEqual(component?.onReceiveMessageArg, message) + XCTAssertNotNil(component?.delegate) + } + func testBridgeIgnoresMessageForInactiveDestination() { let message = Message(id: "1", component: "one", @@ -175,3 +224,24 @@ class BridgeDelegateTests: XCTestCase { jsonData: json) } } + +private final class RedirectedWebView: WKWebView { + init(location: String?) { + self.location = location + super.init(frame: .zero, configuration: WKWebViewConfiguration()) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var url: URL? { + guard let location else { return nil } + + return URL(string: location) + } + + // MARK: Private + + private let location: String? +} diff --git a/Tests/MessageTests.swift b/Tests/MessageTests.swift index 06dc965..8ea51e5 100644 --- a/Tests/MessageTests.swift +++ b/Tests/MessageTests.swift @@ -1,5 +1,5 @@ import XCTest -import Strada +@testable import Strada class MessageTests: XCTestCase { @@ -184,7 +184,7 @@ class MessageTests: XCTestCase { // MARK: Custom encoding - func test_encodingWithCustomEncoder() { + func test_encodingWithCustomEncoder() throws { let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase Strada.config.jsonEncoder = encoder @@ -204,6 +204,15 @@ class MessageTests: XCTestCase { let newMessage = message.replacing(data: messageData) - XCTAssertEqual(message, newMessage) + XCTAssertEqual(message.id, newMessage.id) + XCTAssertEqual(message.event, newMessage.event) + XCTAssertEqual(message.metadata, newMessage.metadata) + + // JSON as a string might have keys in a different order. Parse values to ensure equality. + let newMessageData = try XCTUnwrap(message.jsonData.jsonObject() as? [String: String]) + XCTAssertEqual(newMessageData.keys.count, 3) + XCTAssertEqual(newMessageData["title"], "Page-title") + XCTAssertEqual(newMessageData["subtitle"], "Page-subtitle") + XCTAssertEqual(newMessageData["action_name"], "go") } }