From a29b1ad9bb9cdb2c81416a9d19d03167885fb1bf Mon Sep 17 00:00:00 2001 From: sakuntala_motukuri Date: Thu, 31 Oct 2024 21:33:38 -0400 Subject: [PATCH 01/11] Initial Commit for the beacon plugin --- .../beacon/ios/Sources/BaseBeaconPlugin.swift | 12 ++++++ .../ios/Tests/BaseBeaconPluginTests.swift | 42 +++++++++++++++++++ .../swiftui/Sources/SwiftUIBeaconPlugin.swift | 32 ++++++++------ 3 files changed, 73 insertions(+), 13 deletions(-) diff --git a/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift b/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift index 6b09eada6..95919458e 100644 --- a/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift +++ b/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift @@ -120,4 +120,16 @@ open class BaseBeaconPlugin: JSBasePlugin { else { return } pluginRef?.invokeMethod("beacon", withArguments: [beaconObject]) } + +public struct BeaconPluginHooks { + public let buildBeacon: AsyncHook2 + public let cancelBeacon: Hook2 + + public init(buildBeacon: AsyncHook2, cancelBeacon: Hook2) { + self.buildBeacon = buildBeacon + self.cancelBeacon = cancelBeacon + } +} + + public var hooks: BeaconPluginHooks? } diff --git a/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift b/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift index 6371aa1c3..6aede4dd5 100644 --- a/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift +++ b/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift @@ -136,4 +136,46 @@ class BaseBeaconPluginTests: XCTestCase { )) wait(for: [expectation], timeout: 2) } + + + func testBuildBeaconHook() { + let context = JSContext()! + JSUtilities.polyfill(context) + + let plugin = BaseBeaconPlugin(plugins: []) { _ in } + plugin.context = context + + plugin.hooks?.buildBeacon.tap { (arg1: JSValue, arg2: JSValue) -> JSValue? in + XCTAssertEqual(arg1.toString(), "arg1") + XCTAssertEqual(arg2.toString(), "arg2") + return JSValue(object: "replacement", in: context) + } + + let arg1 = JSValue(object: "arg1", in: context) + let arg2 = JSValue(object: "arg2", in: context) + + XCTAssertNotNil(arg1) + XCTAssertNotNil(arg2) + } + + func testCancelBeaconHook() { + let context = JSContext()! + JSUtilities.polyfill(context) + + let plugin = BaseBeaconPlugin(plugins: []) { _ in } + plugin.context = context + + plugin.hooks?.cancelBeacon.tap { (arg1: JSValue, arg2: JSValue) -> Void in + XCTAssertEqual(arg1.toString(), "arg1") + XCTAssertEqual(arg2.toString(), "arg2") + + return () + } + + let arg1 = JSValue(object: "arg1", in: context) + let arg2 = JSValue(object: "arg2", in: context) + + XCTAssertNotNil(arg1) + XCTAssertNotNil(arg2) + } } diff --git a/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift b/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift index e7877f399..867f92231 100644 --- a/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift +++ b/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift @@ -18,28 +18,34 @@ import PlayerUIBaseBeaconPlugin /** Plugin used by `SwiftUIPlayer` for beaconing in a uniform format between platforms */ -public class BeaconPlugin: BaseBeaconPlugin, NativePlugin { - /** - Constructs a BeaconPlugin - - parameters: - - context: The context to load the plugin into - - onBeacon: A callback to receive beacon events - */ +open class BeaconPlugin: BaseBeaconPlugin, NativePlugin { public convenience init(plugins: [JSBasePlugin] = [], onBeacon: ((BeaconStruct) -> Void)?) { self.init(fileName: "BeaconPlugin.native", pluginName: "BeaconPlugin.BeaconPlugin") self.callback = onBeacon self.plugins = plugins } - public func apply

(player: P) where P: HeadlessPlayer { - guard let player = player as? SwiftUIPlayer else { return } - let beacon = self.beacon(assetBeacon:) - player.hooks?.view.tap(name: "BeaconPlugin") { view in - AnyView(view.environment(\.beaconContext, BeaconContext(beacon))) - } + public var hooks: BeaconPluginHooks? + +open func apply

(player: P) where P: HeadlessPlayer { + + super.apply(player: player) + if let pluginRef = pluginRef { + self.hooks = BeaconPluginHooks( + buildBeacon: AsyncHook2(baseValue: pluginRef, name: "buildBeacon"), + cancelBeacon: Hook2(baseValue: pluginRef, name: "cancelBeacon") + ) + } + + guard let player = player as? SwiftUIPlayer else { return } + let beacon = self.beacon(assetBeacon:) + player.hooks?.view.tap(name: "BeaconPlugin") { view in + AnyView(view.environment(\.beaconContext, BeaconContext(beacon))) } } +} + /** Context object that contains a function to send a beacon */ From 8f4d19c63094ee08f778fff50f55a4e80a92d09e Mon Sep 17 00:00:00 2001 From: sakuntala_motukuri Date: Thu, 31 Oct 2024 21:44:11 -0400 Subject: [PATCH 02/11] Updated beaconplugin --- .../swiftui/Sources/SwiftUIBeaconPlugin.swift | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift b/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift index 867f92231..b97f99780 100644 --- a/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift +++ b/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift @@ -25,25 +25,13 @@ open class BeaconPlugin: BaseBeaconPlugin self.plugins = plugins } - public var hooks: BeaconPluginHooks? - -open func apply

(player: P) where P: HeadlessPlayer { - - super.apply(player: player) - if let pluginRef = pluginRef { - self.hooks = BeaconPluginHooks( - buildBeacon: AsyncHook2(baseValue: pluginRef, name: "buildBeacon"), - cancelBeacon: Hook2(baseValue: pluginRef, name: "cancelBeacon") - ) + open func apply

(player: P) where P: HeadlessPlayer { + guard let player = player as? SwiftUIPlayer else { return } + let beacon = self.beacon(assetBeacon:) + player.hooks?.view.tap(name: "BeaconPlugin") { view in + AnyView(view.environment(\.beaconContext, BeaconContext(beacon))) + } } - - guard let player = player as? SwiftUIPlayer else { return } - let beacon = self.beacon(assetBeacon:) - player.hooks?.view.tap(name: "BeaconPlugin") { view in - AnyView(view.environment(\.beaconContext, BeaconContext(beacon))) - } -} - } /** From b48025b6a73b0558016a32d6cd818a21fc0171c1 Mon Sep 17 00:00:00 2001 From: sakuntala_motukuri Date: Fri, 1 Nov 2024 11:19:09 -0400 Subject: [PATCH 03/11] Initialized beaconpluginhooks --- .../beacon/ios/Sources/BaseBeaconPlugin.swift | 3 ++- .../swiftui/Sources/SwiftUIBeaconPlugin.swift | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift b/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift index 95919458e..7f63eba37 100644 --- a/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift +++ b/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift @@ -52,6 +52,8 @@ public struct DefaultBeacon: Codable, Hashable { Used as a base for framework specific integrations */ open class BaseBeaconPlugin: JSBasePlugin { + + public var hooks: BeaconPluginHooks? /// The callback to call when a beacon is fired from the plugin public var callback: ((BeaconStruct) -> Void)? @@ -131,5 +133,4 @@ public struct BeaconPluginHooks { } } - public var hooks: BeaconPluginHooks? } diff --git a/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift b/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift index b97f99780..99b2db68a 100644 --- a/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift +++ b/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift @@ -19,13 +19,21 @@ import PlayerUIBaseBeaconPlugin Plugin used by `SwiftUIPlayer` for beaconing in a uniform format between platforms */ open class BeaconPlugin: BaseBeaconPlugin, NativePlugin { - public convenience init(plugins: [JSBasePlugin] = [], onBeacon: ((BeaconStruct) -> Void)?) { - self.init(fileName: "BeaconPlugin.native", pluginName: "BeaconPlugin.BeaconPlugin") - self.callback = onBeacon - self.plugins = plugins - } +public convenience init(plugins: [JSBasePlugin] = [], onBeacon: ((BeaconStruct) -> Void)?, hooks: BeaconPluginHooks? = nil) { + self.init(fileName: "BeaconPlugin.native", pluginName: "BeaconPlugin.BeaconPlugin") + self.callback = onBeacon + self.plugins = plugins + self.hooks = hooks +} open func apply

(player: P) where P: HeadlessPlayer { + + if let pluginRef = pluginRef { + self.hooks = BeaconPluginHooks( + buildBeacon: AsyncHook2(baseValue: pluginRef, name: "buildBeacon"), + cancelBeacon: Hook2(baseValue: pluginRef, name: "cancelBeacon") + ) + } guard let player = player as? SwiftUIPlayer else { return } let beacon = self.beacon(assetBeacon:) player.hooks?.view.tap(name: "BeaconPlugin") { view in From 5d80cd0656cca64764722909a0981deaf42baaeb Mon Sep 17 00:00:00 2001 From: sakuntala_motukuri Date: Mon, 4 Nov 2024 18:31:57 -0500 Subject: [PATCH 04/11] Initialized beaconpluginhooks --- .../beacon/ios/Sources/BaseBeaconPlugin.swift | 28 +++++--- .../ios/Tests/BaseBeaconPluginTests.swift | 67 ++++++++++--------- .../swiftui/Sources/SwiftUIBeaconPlugin.swift | 18 ++--- .../ViewInspector/BeaconPluginTests.swift | 52 ++++++++++++++ 4 files changed, 113 insertions(+), 52 deletions(-) diff --git a/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift b/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift index 7f63eba37..2774378bf 100644 --- a/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift +++ b/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift @@ -66,12 +66,23 @@ open class BaseBeaconPlugin: JSBasePlugin { - context: The context to load the plugin into - onBeacon: A callback to receive beacon events */ - public convenience init(plugins: [JSBasePlugin] = [], onBeacon: ((BeaconStruct) -> Void)?) { + public convenience init(plugins: [JSBasePlugin] = [], onBeacon: ((BeaconStruct) -> Void)?) { self.init(fileName: "BeaconPlugin.native", pluginName: "BeaconPlugin.BeaconPlugin") self.callback = onBeacon self.plugins = plugins } + override open func setup(context: JSContext) { + super.setup(context: context) + guard let pluginRef = self.pluginRef else { + fatalError("pluginRef is nil after setup") + } + self.hooks = BeaconPluginHooks( + buildBeacon: AsyncHook2(baseValue: pluginRef, name: "buildBeacon"), + cancelBeacon: Hook2(baseValue: pluginRef, name: "cancelBeacon") + ) + } + override open func getUrlForFile(fileName: String) -> URL? { #if SWIFT_PACKAGE ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle.module) @@ -123,14 +134,13 @@ open class BaseBeaconPlugin: JSBasePlugin { pluginRef?.invokeMethod("beacon", withArguments: [beaconObject]) } -public struct BeaconPluginHooks { - public let buildBeacon: AsyncHook2 - public let cancelBeacon: Hook2 + public struct BeaconPluginHooks { + public let buildBeacon: AsyncHook2 + public let cancelBeacon: Hook2 - public init(buildBeacon: AsyncHook2, cancelBeacon: Hook2) { - self.buildBeacon = buildBeacon - self.cancelBeacon = cancelBeacon + public init(buildBeacon: AsyncHook2, cancelBeacon: Hook2) { + self.buildBeacon = buildBeacon + self.cancelBeacon = cancelBeacon + } } } - -} diff --git a/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift b/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift index 6aede4dd5..797919a15 100644 --- a/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift +++ b/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift @@ -137,45 +137,52 @@ class BaseBeaconPluginTests: XCTestCase { wait(for: [expectation], timeout: 2) } - func testBuildBeaconHook() { - let context = JSContext()! - JSUtilities.polyfill(context) + let context = JSContext()! + JSUtilities.polyfill(context) - let plugin = BaseBeaconPlugin(plugins: []) { _ in } - plugin.context = context + let plugin = BaseBeaconPlugin(plugins: []) { _ in } + plugin.context = context - plugin.hooks?.buildBeacon.tap { (arg1: JSValue, arg2: JSValue) -> JSValue? in - XCTAssertEqual(arg1.toString(), "arg1") - XCTAssertEqual(arg2.toString(), "arg2") - return JSValue(object: "replacement", in: context) - } + guard let hooks = plugin.hooks else { + XCTFail("Hooks are not initialized") + return + } - let arg1 = JSValue(object: "arg1", in: context) - let arg2 = JSValue(object: "arg2", in: context) + hooks.buildBeacon.tap { (arg1: JSValue, arg2: JSValue) -> JSValue? in + XCTAssertEqual(arg1.toString(), "arg1") + XCTAssertEqual(arg2.toString(), "arg2") + return JSValue(object: "replacement", in: context) + } - XCTAssertNotNil(arg1) - XCTAssertNotNil(arg2) - } + let arg1 = JSValue(object: "arg1", in: context) + let arg2 = JSValue(object: "arg2", in: context) - func testCancelBeaconHook() { - let context = JSContext()! - JSUtilities.polyfill(context) + XCTAssertNotNil(arg1) + XCTAssertNotNil(arg2) + } - let plugin = BaseBeaconPlugin(plugins: []) { _ in } - plugin.context = context + func testCancelBeaconHook() { + let context = JSContext()! + JSUtilities.polyfill(context) - plugin.hooks?.cancelBeacon.tap { (arg1: JSValue, arg2: JSValue) -> Void in - XCTAssertEqual(arg1.toString(), "arg1") - XCTAssertEqual(arg2.toString(), "arg2") + let plugin = BaseBeaconPlugin(plugins: []) { _ in } + plugin.context = context - return () - } + guard let hooks = plugin.hooks else { + XCTFail("Hooks are not initialized") + return + } - let arg1 = JSValue(object: "arg1", in: context) - let arg2 = JSValue(object: "arg2", in: context) + hooks.cancelBeacon.tap { (arg1: JSValue, arg2: JSValue) -> Void in + XCTAssertEqual(arg1.toString(), "arg1") + XCTAssertEqual(arg2.toString(), "arg2") + } - XCTAssertNotNil(arg1) - XCTAssertNotNil(arg2) - } + let arg1 = JSValue(object: "arg1", in: context) + let arg2 = JSValue(object: "arg2", in: context) + + XCTAssertNotNil(arg1) + XCTAssertNotNil(arg2) + } } diff --git a/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift b/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift index 99b2db68a..b97f99780 100644 --- a/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift +++ b/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift @@ -19,21 +19,13 @@ import PlayerUIBaseBeaconPlugin Plugin used by `SwiftUIPlayer` for beaconing in a uniform format between platforms */ open class BeaconPlugin: BaseBeaconPlugin, NativePlugin { -public convenience init(plugins: [JSBasePlugin] = [], onBeacon: ((BeaconStruct) -> Void)?, hooks: BeaconPluginHooks? = nil) { - self.init(fileName: "BeaconPlugin.native", pluginName: "BeaconPlugin.BeaconPlugin") - self.callback = onBeacon - self.plugins = plugins - self.hooks = hooks -} + public convenience init(plugins: [JSBasePlugin] = [], onBeacon: ((BeaconStruct) -> Void)?) { + self.init(fileName: "BeaconPlugin.native", pluginName: "BeaconPlugin.BeaconPlugin") + self.callback = onBeacon + self.plugins = plugins + } open func apply

(player: P) where P: HeadlessPlayer { - - if let pluginRef = pluginRef { - self.hooks = BeaconPluginHooks( - buildBeacon: AsyncHook2(baseValue: pluginRef, name: "buildBeacon"), - cancelBeacon: Hook2(baseValue: pluginRef, name: "cancelBeacon") - ) - } guard let player = player as? SwiftUIPlayer else { return } let beacon = self.beacon(assetBeacon:) player.hooks?.view.tap(name: "BeaconPlugin") { view in diff --git a/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift b/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift index 00723351f..ac07c9f92 100644 --- a/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift +++ b/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift @@ -10,6 +10,7 @@ import Foundation import XCTest import SwiftUI import ViewInspector +import JavaScriptCore @testable import PlayerUI @testable import PlayerUIInternalTestUtilities @testable import PlayerUISwiftUI @@ -17,6 +18,7 @@ import ViewInspector @testable import PlayerUIBaseBeaconPlugin @testable import PlayerUIBeaconPlugin + class BeaconPluginTests: XCTestCase { override func setUp() { XCUIApplication().terminate() @@ -97,6 +99,56 @@ class BeaconPluginTests: XCTestCase { ViewHosting.expel() } + + + func testCancelsSpecificBeaconsUsingHooks() { + let handlerExpectation = expectation(description: "Handler called") + let handler = MockHandler() + let plugin = BeaconPlugin(plugins: []) { (beacon) in + handler.handle(beacon.viewId!, beacon.data) + } + + let context = JSContext()! + JSUtilities.polyfill(context) + plugin.context = context + plugin.setup(context: context) + + guard let hooks = plugin.hooks else { + XCTFail("Hooks are not initialized") + return + } + + hooks.cancelBeacon.tap { (arg1: JSValue, arg2: JSValue) -> Void in + if arg1.toString() == "view-1" { + return + } + } + + let player = SwiftUIPlayer( + flow: FlowData.COUNTER, + plugins: [ReferenceAssetsPlugin(), plugin] + ) + ViewHosting.host(view: player) + +// let asset = BeaconableAsset(id: "test", metaData: nil) +// let assetBeacon = AssetBeacon(action: "clicked", element: "button", asset: asset, viewId: "view-1", data: nil) +// plugin.beacon(assetBeacon: assetBeacon) + + wait(for: [handlerExpectation], timeout: 10) +// XCTAssertEqual(handler.calls.count, 1) +// XCTAssertEqual(handler.calls[0].0, "view-2") // Accessing the correct element from the tuple + + ViewHosting.expel() + } +} + +class MockHandler { + var calls: [(String, Any?)] = [] + + func handle(_ action: String, _ data: Any? = nil) { + calls.append((action, data)) + } + } struct TestButton: View { From eb8b94fd8413debacc29efdde157c2a5edb5b1f3 Mon Sep 17 00:00:00 2001 From: sakuntala_motukuri Date: Mon, 4 Nov 2024 21:29:13 -0500 Subject: [PATCH 05/11] Initialized BaseBeaconPluginHooks and updated tests accordingly --- ios/core/Sources/Types/Hooks/Hook.swift | 60 +++--- .../beacon/ios/Sources/BaseBeaconPlugin.swift | 44 ++--- .../ios/Tests/BaseBeaconPluginTests.swift | 87 ++------ .../swiftui/Sources/SwiftUIBeaconPlugin.swift | 28 +-- .../ViewInspector/BeaconPluginTests.swift | 185 +++++++++--------- 5 files changed, 179 insertions(+), 225 deletions(-) diff --git a/ios/core/Sources/Types/Hooks/Hook.swift b/ios/core/Sources/Types/Hooks/Hook.swift index 6510ae7a1..d89319e93 100644 --- a/ios/core/Sources/Types/Hooks/Hook.swift +++ b/ios/core/Sources/Types/Hooks/Hook.swift @@ -11,16 +11,16 @@ import JavaScriptCore /// A base for implementing JS backed hooks open class BaseJSHook { private let baseValue: JSValue - + /// The JS reference to the hook public var hook: JSValue { baseValue.objectForKeyedSubscript("hooks").objectForKeyedSubscript(name) } - + /// The JSContext for the hook public var context: JSContext { hook.context } - + /// The name of the hook public let name: String - + /// Retrieves a hook by name from an object in JS /// - Parameters: /// - baseValue: The object that has `hooks` @@ -39,7 +39,7 @@ public class Hook: BaseJSHook where T: CreatedFromJSValue { /** Attach a closure to the hook, so when the hook is fired in the JS runtime we receive the event in the native runtime - + - parameters: - hook: A function to run when the JS hook is fired */ @@ -51,7 +51,7 @@ public class Hook: BaseJSHook where T: CreatedFromJSValue { else { return } hook(hookValue) } - + self.hook.invokeMethod("tap", withArguments: [name, JSValue(object: tapMethod, in: context) as Any]) } } @@ -64,21 +64,21 @@ public class Hook2: BaseJSHook where T: CreatedFromJSValue, U: CreatedFrom /** Attach a closure to the hook, so when the hook is fired in the JS runtime we receive the event in the native runtime - + - parameters: - hook: A function to run when the JS hook is fired */ - public func tap(_ hook: @escaping (T, U) -> Void) { - let tapMethod: @convention(block) (JSValue?, JSValue?) -> Void = { value, value2 in + public func tap(_ hook: @escaping (T, U) -> R) { + let tapMethod: @convention(block) (JSValue?, JSValue?) -> Any? = { value, value2 in guard let val = value, let val2 = value2, let hookValue = T.createInstance(value: val) as? T, let hookValue2 = U.createInstance(value: val2) as? U - else { return } - hook(hookValue, hookValue2) + else { return nil } + return hook(hookValue, hookValue2) } - + self.hook.invokeMethod("tap", withArguments: [name, JSValue(object: tapMethod, in: context) as Any]) } } @@ -91,7 +91,7 @@ public class HookDecode: BaseJSHook where T: Decodable { /** Attach a closure to the hook, so when the hook is fired in the JS runtime we receive the event in the native runtime - + - parameters: - hook: A function to run when the JS hook is fired */ @@ -103,7 +103,7 @@ public class HookDecode: BaseJSHook where T: Decodable { else { return } hook(hookValue) } - + self.hook.invokeMethod("tap", withArguments: [name, JSValue(object: tapMethod, in: context) as Any]) } } @@ -116,13 +116,13 @@ public class Hook2Decode: BaseJSHook where T: Decodable, U: Decodable { /** Attach a closure to the hook, so when the hook is fired in the JS runtime we receive the event in the native runtime - + - parameters: - hook: A function to run when the JS hook is fired */ public func tap(_ hook: @escaping (T, U) -> Void) { let tapMethod: @convention(block) (JSValue?, JSValue?) -> Void = { value, value2 in - + let decoder = JSONDecoder() guard let val = value, @@ -132,7 +132,7 @@ public class Hook2Decode: BaseJSHook where T: Decodable, U: Decodable { else { return } hook(hookValue, hookValue2) } - + self.hook.invokeMethod("tap", withArguments: [name, JSValue(object: tapMethod, in: context) as Any]) } } @@ -143,13 +143,13 @@ public class Hook2Decode: BaseJSHook where T: Decodable, U: Decodable { */ public class AsyncHook: BaseJSHook where T: CreatedFromJSValue { private var handler: AsyncHookHandler? - + public typealias AsyncHookHandler = (T) async throws -> JSValue? - + /** Attach a closure to the hook, so when the hook is fired in the JS runtime we receive the event in the native runtime - + - parameters: - hook: A function to run when the JS hook is fired */ @@ -159,7 +159,7 @@ public class AsyncHook: BaseJSHook where T: CreatedFromJSValue { let val = value, let hookValue = T.createInstance(value: val) as? T else { return JSValue() } - + let promise = JSUtilities.createPromise(context: self.context, handler: { (resolve, _) in Task { @@ -169,10 +169,10 @@ public class AsyncHook: BaseJSHook where T: CreatedFromJSValue { } } }) - + return promise ?? JSValue() } - + self.hook.invokeMethod("tap", withArguments: [name, JSValue(object: tapMethod, in: context) as Any]) } } @@ -184,13 +184,13 @@ public class AsyncHook: BaseJSHook where T: CreatedFromJSValue { */ public class AsyncHook2: BaseJSHook where T: CreatedFromJSValue, U: CreatedFromJSValue { private var handler: AsyncHookHandler? - + public typealias AsyncHookHandler = (T, U) async throws -> JSValue? - + /** Attach a closure to the hook, so when the hook is fired in the JS runtime we receive the event in the native runtime - + - parameters: - hook: A function to run when the JS hook is fired */ @@ -202,8 +202,8 @@ public class AsyncHook2: BaseJSHook where T: CreatedFromJSValue, U: Create let hookValue = T.createInstance(value: val) as? T, let hookValue2 = U.createInstance(value: val2) as? U else { return JSValue() } - - + + let promise = JSUtilities.createPromise(context: self.context, handler: { (resolve, _) in Task { @@ -213,10 +213,10 @@ public class AsyncHook2: BaseJSHook where T: CreatedFromJSValue, U: Create } } }) - + return promise ?? JSValue() } - + self.hook.invokeMethod("tap", withArguments: [name, JSValue(object: tapMethod, in: context) as Any]) } } diff --git a/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift b/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift index 2774378bf..9f7c123c7 100644 --- a/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift +++ b/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift @@ -18,19 +18,19 @@ import PlayerUI public struct DefaultBeacon: Codable, Hashable { /// The action the user performed public let action: String - + /// The element that performed the action public let element: String - + /// The ID of the asset triggering the beacon public let assetId: String? - + /// The ID of the view triggering the beacon public let viewId: String? - + /// Additional data added from the asset or metaData public let data: AnyType? - + /// Construct a ``DefaultBeacon`` /// - Parameters: /// - action: The action the user performed @@ -52,26 +52,26 @@ public struct DefaultBeacon: Codable, Hashable { Used as a base for framework specific integrations */ open class BaseBeaconPlugin: JSBasePlugin { - + public var hooks: BeaconPluginHooks? /// The callback to call when a beacon is fired from the plugin public var callback: ((BeaconStruct) -> Void)? - + /// BeaconPluginPlugin's to use for this BeaconPlugin public var plugins: [JSBasePlugin] = [] - + /** Constructs a BeaconPlugin - parameters: - - context: The context to load the plugin into - - onBeacon: A callback to receive beacon events + - context: The context to load the plugin into + - onBeacon: A callback to receive beacon events */ - public convenience init(plugins: [JSBasePlugin] = [], onBeacon: ((BeaconStruct) -> Void)?) { + public convenience init(plugins: [JSBasePlugin] = [], onBeacon: ((BeaconStruct) -> Void)?) { self.init(fileName: "BeaconPlugin.native", pluginName: "BeaconPlugin.BeaconPlugin") self.callback = onBeacon self.plugins = plugins } - + override open func setup(context: JSContext) { super.setup(context: context) guard let pluginRef = self.pluginRef else { @@ -82,20 +82,20 @@ open class BaseBeaconPlugin: JSBasePlugin { cancelBeacon: Hook2(baseValue: pluginRef, name: "cancelBeacon") ) } - + override open func getUrlForFile(fileName: String) -> URL? { - #if SWIFT_PACKAGE +#if SWIFT_PACKAGE ResourceUtilities.urlForFile(name: fileName, ext: "js", bundle: Bundle.module) - #else +#else ResourceUtilities.urlForFile( name: fileName, ext: "js", bundle: Bundle(for: BaseBeaconPlugin.self), pathComponent: "PlayerUI_BaseBeaconPlugin.bundle" ) - #endif +#endif } - + /** Retrieves the arguments for constructing this plugin, this is necessary because the arguments need to be supplied after construction of the swift object, once the context has been provided @@ -118,12 +118,12 @@ open class BaseBeaconPlugin: JSBasePlugin { let jsCallback = JSValue(object: callback, in: context) as Any return [["callback": jsCallback, "plugins": plugins.map { $0.pluginRef }]] } - + /** Function to send a beacon event through the plugin for processing - parameters: - - action: The action that was taken for the beacon - - args: The context of the beacon + - action: The action that was taken for the beacon + - args: The context of the beacon */ public func beacon(assetBeacon: AssetBeacon) { guard @@ -133,11 +133,11 @@ open class BaseBeaconPlugin: JSBasePlugin { else { return } pluginRef?.invokeMethod("beacon", withArguments: [beaconObject]) } - + public struct BeaconPluginHooks { public let buildBeacon: AsyncHook2 public let cancelBeacon: Hook2 - + public init(buildBeacon: AsyncHook2, cancelBeacon: Hook2) { self.buildBeacon = buildBeacon self.cancelBeacon = cancelBeacon diff --git a/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift b/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift index 797919a15..40a62f8d6 100644 --- a/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift +++ b/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift @@ -14,18 +14,18 @@ import JavaScriptCore class BPPlugin: JSBasePlugin { override open func setup(context: JSContext) { - + } } class BaseBeaconPluginTests: XCTestCase { func testBundleLoadsSymbols() { let context = JSContext()! - + let plugin = BaseBeaconPlugin() { _ in } - + plugin.context = context - + // as accessed on main from beacon-plugin.prod.js XCTAssertFalse(context.objectForKeyedSubscript("BeaconPlugin").objectForKeyedSubscript("BeaconPlugin").isUndefined) XCTAssertFalse(context.objectForKeyedSubscript("BeaconPlugin").objectForKeyedSubscript("BeaconPluginSymbol").isUndefined) @@ -34,30 +34,30 @@ class BaseBeaconPluginTests: XCTestCase { let context = JSContext()! JSUtilities.polyfill(context) let bpp = BPPlugin(fileName: "", pluginName: "") - + let plugin = BaseBeaconPlugin(plugins: [bpp]) { _ in } plugin.context = context - + XCTAssertNotNil(bpp.context) } - + func testBeaconFunction() { let context = JSContext()! JSUtilities.polyfill(context) - + let beaconed = expectation(description: "Beacon sent") - + let plugin = BaseBeaconPlugin(plugins: []) { _ in beaconed.fulfill() } plugin.context = context - + plugin.beacon(assetBeacon: AssetBeacon(action: "action", element: "element", asset: BeaconableAsset(id: "id"))) wait(for: [beaconed], timeout: 1) } - + func testBeaconPluginStringData() { let context = JSContext()! JSUtilities.polyfill(context) - + let expectation = XCTestExpectation(description: "beacon callback called") let plugin = BaseBeaconPlugin( onBeacon: { (beacon) in XCTAssertEqual(beacon.assetId, "test") @@ -71,7 +71,7 @@ class BaseBeaconPluginTests: XCTestCase { expectation.fulfill() }) plugin.context = context - + plugin.beacon(assetBeacon: AssetBeacon( action: BeaconAction.clicked.rawValue, element: BeaconElement.button.rawValue, @@ -80,11 +80,11 @@ class BaseBeaconPluginTests: XCTestCase { )) wait(for: [expectation], timeout: 2) } - + func testBeaconPluginDictionaryData() { let context = JSContext()! JSUtilities.polyfill(context) - + let expectation = XCTestExpectation(description: "beacon callback called") let plugin = BaseBeaconPlugin(onBeacon: { (beacon) in XCTAssertEqual(beacon.assetId, "test") @@ -98,7 +98,7 @@ class BaseBeaconPluginTests: XCTestCase { expectation.fulfill() }) plugin.context = context - + plugin.beacon(assetBeacon: AssetBeacon( action: BeaconAction.clicked.rawValue, element: BeaconElement.button.rawValue, @@ -107,11 +107,11 @@ class BaseBeaconPluginTests: XCTestCase { )) wait(for: [expectation], timeout: 2) } - + func testBeaconPluginAnyDictionaryData() { let context = JSContext()! JSUtilities.polyfill(context) - + let expectation = XCTestExpectation(description: "beacon callback called") let plugin = BaseBeaconPlugin(onBeacon: { (beacon) in XCTAssertEqual(beacon.assetId, "test") @@ -127,7 +127,7 @@ class BaseBeaconPluginTests: XCTestCase { expectation.fulfill() }) plugin.context = context - + plugin.beacon(assetBeacon: AssetBeacon( action: BeaconAction.clicked.rawValue, element: BeaconElement.button.rawValue, @@ -136,53 +136,4 @@ class BaseBeaconPluginTests: XCTestCase { )) wait(for: [expectation], timeout: 2) } - - func testBuildBeaconHook() { - let context = JSContext()! - JSUtilities.polyfill(context) - - let plugin = BaseBeaconPlugin(plugins: []) { _ in } - plugin.context = context - - guard let hooks = plugin.hooks else { - XCTFail("Hooks are not initialized") - return - } - - hooks.buildBeacon.tap { (arg1: JSValue, arg2: JSValue) -> JSValue? in - XCTAssertEqual(arg1.toString(), "arg1") - XCTAssertEqual(arg2.toString(), "arg2") - return JSValue(object: "replacement", in: context) - } - - let arg1 = JSValue(object: "arg1", in: context) - let arg2 = JSValue(object: "arg2", in: context) - - XCTAssertNotNil(arg1) - XCTAssertNotNil(arg2) - } - - func testCancelBeaconHook() { - let context = JSContext()! - JSUtilities.polyfill(context) - - let plugin = BaseBeaconPlugin(plugins: []) { _ in } - plugin.context = context - - guard let hooks = plugin.hooks else { - XCTFail("Hooks are not initialized") - return - } - - hooks.cancelBeacon.tap { (arg1: JSValue, arg2: JSValue) -> Void in - XCTAssertEqual(arg1.toString(), "arg1") - XCTAssertEqual(arg2.toString(), "arg2") - } - - let arg1 = JSValue(object: "arg1", in: context) - let arg2 = JSValue(object: "arg2", in: context) - - XCTAssertNotNil(arg1) - XCTAssertNotNil(arg2) - } } diff --git a/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift b/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift index b97f99780..cd87995a2 100644 --- a/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift +++ b/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift @@ -24,7 +24,7 @@ open class BeaconPlugin: BaseBeaconPlugin self.callback = onBeacon self.plugins = plugins } - + open func apply

(player: P) where P: HeadlessPlayer { guard let player = player as? SwiftUIPlayer else { return } let beacon = self.beacon(assetBeacon:) @@ -39,24 +39,24 @@ open class BeaconPlugin: BaseBeaconPlugin */ public class BeaconContext: ObservableObject { private let beaconFn: (AssetBeacon) -> Void - + /** Constructs a BeaconContext - parameters: - - beacon: The function to use for sending the beacon + - beacon: The function to use for sending the beacon */ public init(_ beacon: @escaping (AssetBeacon) -> Void) { self.beaconFn = beacon } - + /** Sends a beacon through the JavaScript beacon plugin - parameters: - - action: The type of action that occurred - - element: The type of element in the asset that triggered the beacon - - id: The ID of the asset that triggered the beacon - - metaData: `BeaconableMetaData` to include as extra data to the core BeaconPlugin - - data: Additional arbitrary data to include in the beacon + - action: The type of action that occurred + - element: The type of element in the asset that triggered the beacon + - id: The ID of the asset that triggered the beacon + - metaData: `BeaconableMetaData` to include as extra data to the core BeaconPlugin + - data: Additional arbitrary data to include in the beacon */ public func beacon( action: String, @@ -75,14 +75,14 @@ public class BeaconContext: ObservableObject { ) ) } - + /** Sends a beacon through the JavaScript beacon plugin - parameters: - - action: The type of action that occurred - - element: The type of element in the asset that triggered the beacon - - id: The ID of the asset that triggered the beacon - - data: Additional arbitrary data to include in the beacon + - action: The type of action that occurred + - element: The type of element in the asset that triggered the beacon + - id: The ID of the asset that triggered the beacon + - data: Additional arbitrary data to include in the beacon */ public func beacon( action: String, diff --git a/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift b/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift index ac07c9f92..f918a7e84 100644 --- a/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift +++ b/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift @@ -23,132 +23,135 @@ class BeaconPluginTests: XCTestCase { override func setUp() { XCUIApplication().terminate() } -// func testContextAttachment() throws { -// let player = SwiftUIPlayer(flow: "", plugins: [BeaconPlugin { _ in}]) -// -// guard let view: AnyView = player.hooks?.view.call(AnyView(TestButton())) else { -// return XCTFail("no view returned from hook") -// } -// -// // Should be wrapped in 2 anyviews, one for playercontrollers, one for beaconcontext -// _ = try view.inspect().anyView().anyView().view(TestButton.self) -// } -// func testBeaconContext() throws { -// let expect = expectation(description: "Beacon Called") -// let beacon: (AssetBeacon) -> Void = { (beaconObj: AssetBeacon) in -// guard -// beaconObj.action == "clicked", -// beaconObj.element == "button", -// beaconObj.asset.id == "test" -// else { return XCTFail("incorrect beacon information") } -// expect.fulfill() -// } -// -// let context = BeaconContext(beacon) -// var tree = TestButton() -// -// let exp = tree.on(\.didAppear) { view in -// try view.button().tap() -// } -// -// ViewHosting.host(view: tree.environment(\.beaconContext, context)) -// -// wait(for: [exp, expect], timeout: 10) -// } -// -// func testBeaconContextWithMetaData() throws { -// let expect = expectation(description: "Beacon Called") -// let beacon: (AssetBeacon) -> Void = { (beaconObj: AssetBeacon) in -// guard -// beaconObj.action == "clicked", -// beaconObj.element == "button", -// beaconObj.asset.id == "test", -// case .dictionary(let data) = beaconObj.asset.metaData?.beacon, -// data["field"] == "value" -// else { return XCTFail("incorrect beacon information") } -// expect.fulfill() -// } -// -// let context = BeaconContext(beacon) -// let data = MetaData(beacon: .dictionary(data: ["field": "value"])) -// var tree = TestButton(metaData: data) -// -// let exp = tree.on(\.didAppear) { view in -// try view.button().tap() -// } -// -// ViewHosting.host(view: tree.environment(\.beaconContext, context)) -// -// wait(for: [exp, expect], timeout: 10) -// } - + // func testContextAttachment() throws { + // let player = SwiftUIPlayer(flow: "", plugins: [BeaconPlugin { _ in}]) + // + // guard let view: AnyView = player.hooks?.view.call(AnyView(TestButton())) else { + // return XCTFail("no view returned from hook") + // } + // + // // Should be wrapped in 2 anyviews, one for playercontrollers, one for beaconcontext + // _ = try view.inspect().anyView().anyView().view(TestButton.self) + // } + // func testBeaconContext() throws { + // let expect = expectation(description: "Beacon Called") + // let beacon: (AssetBeacon) -> Void = { (beaconObj: AssetBeacon) in + // guard + // beaconObj.action == "clicked", + // beaconObj.element == "button", + // beaconObj.asset.id == "test" + // else { return XCTFail("incorrect beacon information") } + // expect.fulfill() + // } + // + // let context = BeaconContext(beacon) + // var tree = TestButton() + // + // let exp = tree.on(\.didAppear) { view in + // try view.button().tap() + // } + // + // ViewHosting.host(view: tree.environment(\.beaconContext, context)) + // + // wait(for: [exp, expect], timeout: 10) + // } + // + // func testBeaconContextWithMetaData() throws { + // let expect = expectation(description: "Beacon Called") + // let beacon: (AssetBeacon) -> Void = { (beaconObj: AssetBeacon) in + // guard + // beaconObj.action == "clicked", + // beaconObj.element == "button", + // beaconObj.asset.id == "test", + // case .dictionary(let data) = beaconObj.asset.metaData?.beacon, + // data["field"] == "value" + // else { return XCTFail("incorrect beacon information") } + // expect.fulfill() + // } + // + // let context = BeaconContext(beacon) + // let data = MetaData(beacon: .dictionary(data: ["field": "value"])) + // var tree = TestButton(metaData: data) + // + // let exp = tree.on(\.didAppear) { view in + // try view.button().tap() + // } + // + // ViewHosting.host(view: tree.environment(\.beaconContext, context)) + // + // wait(for: [exp, expect], timeout: 10) + // } + func testSendsViewBeacon() { let beaconed = expectation(description: "View beacon called") let plugin = BeaconPlugin(plugins: []) { (beacon) in guard beacon.action == "viewed" else { return } beaconed.fulfill() } - + let player = SwiftUIPlayer( flow: FlowData.COUNTER, plugins: [ReferenceAssetsPlugin(), plugin] ) ViewHosting.host(view: player) - + wait(for: [beaconed], timeout: 10) - + ViewHosting.expel() } - - - func testCancelsSpecificBeaconsUsingHooks() { - let handlerExpectation = expectation(description: "Handler called") + + + func testBuildCancelsSpecificBeaconsUsingHooks() { + let handlerExpectation = expectation(description: "Beacon Handler called") let handler = MockHandler() let plugin = BeaconPlugin(plugins: []) { (beacon) in handler.handle(beacon.viewId!, beacon.data) + handlerExpectation.fulfill() } - + let context = JSContext()! JSUtilities.polyfill(context) plugin.context = context plugin.setup(context: context) - + guard let hooks = plugin.hooks else { XCTFail("Hooks are not initialized") return } - - hooks.cancelBeacon.tap { (arg1: JSValue, arg2: JSValue) -> Void in - if arg1.toString() == "view-1" { - return + + hooks.buildBeacon.tap { (arg1: JSValue, arg2: JSValue) -> JSValue? in + if arg1.toString() == "view-1" { + return JSValue(bool: true, in: context) + } + return JSValue(bool: false, in: context) } - } - - let player = SwiftUIPlayer( - flow: FlowData.COUNTER, - plugins: [ReferenceAssetsPlugin(), plugin] - ) - ViewHosting.host(view: player) - -// let asset = BeaconableAsset(id: "test", metaData: nil) -// let assetBeacon = AssetBeacon(action: "clicked", element: "button", asset: asset, viewId: "view-1", data: nil) -// plugin.beacon(assetBeacon: assetBeacon) - - wait(for: [handlerExpectation], timeout: 10) -// XCTAssertEqual(handler.calls.count, 1) -// XCTAssertEqual(handler.calls[0].0, "view-2") // Accessing the correct element from the tuple - - ViewHosting.expel() + + hooks.cancelBeacon.tap { (arg1: JSValue, arg2: JSValue) -> Bool in + if arg1.toString() == "view-1" { + return true + } + return false + } + + let player = SwiftUIPlayer( + flow: FlowData.COUNTER, + plugins: [ReferenceAssetsPlugin(), plugin] + ) + ViewHosting.host(view: player) + + wait(for: [handlerExpectation], timeout: 10) + + ViewHosting.expel() } } class MockHandler { var calls: [(String, Any?)] = [] - + func handle(_ action: String, _ data: Any? = nil) { calls.append((action, data)) } - + } struct TestButton: View { From 9231f9e4feff72e242703bde1ebef24f1e768007 Mon Sep 17 00:00:00 2001 From: sakuntala_motukuri Date: Wed, 6 Nov 2024 11:53:58 -0500 Subject: [PATCH 06/11] changed jscontext --- .../ViewInspector/BeaconPluginTests.swift | 173 ++++++------------ 1 file changed, 56 insertions(+), 117 deletions(-) diff --git a/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift b/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift index f918a7e84..684ae894d 100644 --- a/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift +++ b/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift @@ -18,131 +18,70 @@ import JavaScriptCore @testable import PlayerUIBaseBeaconPlugin @testable import PlayerUIBeaconPlugin +extension Inspection: InspectionEmissary { } class BeaconPluginTests: XCTestCase { override func setUp() { XCUIApplication().terminate() } - // func testContextAttachment() throws { - // let player = SwiftUIPlayer(flow: "", plugins: [BeaconPlugin { _ in}]) - // - // guard let view: AnyView = player.hooks?.view.call(AnyView(TestButton())) else { - // return XCTFail("no view returned from hook") - // } - // - // // Should be wrapped in 2 anyviews, one for playercontrollers, one for beaconcontext - // _ = try view.inspect().anyView().anyView().view(TestButton.self) - // } - // func testBeaconContext() throws { - // let expect = expectation(description: "Beacon Called") - // let beacon: (AssetBeacon) -> Void = { (beaconObj: AssetBeacon) in - // guard - // beaconObj.action == "clicked", - // beaconObj.element == "button", - // beaconObj.asset.id == "test" - // else { return XCTFail("incorrect beacon information") } - // expect.fulfill() - // } - // - // let context = BeaconContext(beacon) - // var tree = TestButton() - // - // let exp = tree.on(\.didAppear) { view in - // try view.button().tap() - // } - // - // ViewHosting.host(view: tree.environment(\.beaconContext, context)) - // - // wait(for: [exp, expect], timeout: 10) - // } - // - // func testBeaconContextWithMetaData() throws { - // let expect = expectation(description: "Beacon Called") - // let beacon: (AssetBeacon) -> Void = { (beaconObj: AssetBeacon) in - // guard - // beaconObj.action == "clicked", - // beaconObj.element == "button", - // beaconObj.asset.id == "test", - // case .dictionary(let data) = beaconObj.asset.metaData?.beacon, - // data["field"] == "value" - // else { return XCTFail("incorrect beacon information") } - // expect.fulfill() - // } - // - // let context = BeaconContext(beacon) - // let data = MetaData(beacon: .dictionary(data: ["field": "value"])) - // var tree = TestButton(metaData: data) - // - // let exp = tree.on(\.didAppear) { view in - // try view.button().tap() - // } - // - // ViewHosting.host(view: tree.environment(\.beaconContext, context)) - // - // wait(for: [exp, expect], timeout: 10) - // } - - func testSendsViewBeacon() { - let beaconed = expectation(description: "View beacon called") - let plugin = BeaconPlugin(plugins: []) { (beacon) in - guard beacon.action == "viewed" else { return } - beaconed.fulfill() - } - - let player = SwiftUIPlayer( - flow: FlowData.COUNTER, - plugins: [ReferenceAssetsPlugin(), plugin] - ) - ViewHosting.host(view: player) - - wait(for: [beaconed], timeout: 10) - - ViewHosting.expel() + +func testBuildCancelsSpecificBeaconsUsingHooks() { + let handlerExpectation = expectation(description: "Beacon Handler called") + let cancelBeaconHandler = expectation(description: "Cancel Beacon Handler called") + let buildBeaconHandler = expectation(description: "Build Beacon Handler called") + + let handler = MockHandler() + let plugin = BeaconPlugin(plugins: []) { (beacon) in + print("Beacon: \(beacon)") + handler.handle(beacon.viewId!, beacon.data) + handlerExpectation.fulfill() } - - - func testBuildCancelsSpecificBeaconsUsingHooks() { - let handlerExpectation = expectation(description: "Beacon Handler called") - let handler = MockHandler() - let plugin = BeaconPlugin(plugins: []) { (beacon) in - handler.handle(beacon.viewId!, beacon.data) - handlerExpectation.fulfill() - } - - let context = JSContext()! - JSUtilities.polyfill(context) - plugin.context = context - plugin.setup(context: context) - - guard let hooks = plugin.hooks else { - XCTFail("Hooks are not initialized") - return - } - - hooks.buildBeacon.tap { (arg1: JSValue, arg2: JSValue) -> JSValue? in - if arg1.toString() == "view-1" { - return JSValue(bool: true, in: context) - } - return JSValue(bool: false, in: context) - } - - hooks.cancelBeacon.tap { (arg1: JSValue, arg2: JSValue) -> Bool in - if arg1.toString() == "view-1" { + + let context = JSContext()! + JSUtilities.polyfill(context) + plugin.context = context + plugin.setup(context: context) + + guard let hooks = plugin.hooks else { + XCTFail("Hooks are not initialized") + return + } + + hooks.buildBeacon.tap { (arg1: JSValue, arg2: JSValue) -> JSValue? in + buildBeaconHandler.fulfill() + if let action = arg1.toDictionary()?["action"] as? String, action == "viewed" { + return JSValue(bool: true, in: context) + } + return JSValue(bool: false, in: context) + } + + hooks.cancelBeacon.tap { (arg1: JSValue, arg2: JSValue) -> Bool in + cancelBeaconHandler.fulfill() + + if let action = arg1.toDictionary()?["action"] as? String, action == "viewed" { return true - } - return false - } - - let player = SwiftUIPlayer( - flow: FlowData.COUNTER, - plugins: [ReferenceAssetsPlugin(), plugin] - ) - ViewHosting.host(view: player) - - wait(for: [handlerExpectation], timeout: 10) - - ViewHosting.expel() + } + return false } + + let playerContext = SwiftUIPlayer.Context { context } + + let player = SwiftUIPlayer( + flow: FlowData.COUNTER, + plugins: [ReferenceAssetsPlugin(), plugin], + context: playerContext + ) + ViewHosting.host(view: player) + + let viewExpectation = player.inspection.inspect(after: 1.0) { view in + _ = try view.vStack().first?.anyView().find(text: "Clicked 0 times") + } + + wait(for: [handlerExpectation, cancelBeaconHandler, buildBeaconHandler,viewExpectation], timeout: 10) + + ViewHosting.expel() +} + } class MockHandler { From 138d4578ca9f69cce5e378632e82ed0135d7ea6d Mon Sep 17 00:00:00 2001 From: sakuntala_motukuri Date: Wed, 6 Nov 2024 17:17:54 -0500 Subject: [PATCH 07/11] updated tests --- .../ViewInspector/BeaconPluginTests.swift | 87 +++++++++---------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift b/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift index 684ae894d..42d6e4f5f 100644 --- a/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift +++ b/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift @@ -25,63 +25,62 @@ class BeaconPluginTests: XCTestCase { XCUIApplication().terminate() } -func testBuildCancelsSpecificBeaconsUsingHooks() { - let handlerExpectation = expectation(description: "Beacon Handler called") - let cancelBeaconHandler = expectation(description: "Cancel Beacon Handler called") - let buildBeaconHandler = expectation(description: "Build Beacon Handler called") - - let handler = MockHandler() - let plugin = BeaconPlugin(plugins: []) { (beacon) in - print("Beacon: \(beacon)") - handler.handle(beacon.viewId!, beacon.data) - handlerExpectation.fulfill() - } - let context = JSContext()! - JSUtilities.polyfill(context) - plugin.context = context - plugin.setup(context: context) + func testBuildCancelsSpecificBeaconsUsingHooks() { - guard let hooks = plugin.hooks else { - XCTFail("Hooks are not initialized") - return - } - hooks.buildBeacon.tap { (arg1: JSValue, arg2: JSValue) -> JSValue? in - buildBeaconHandler.fulfill() - if let action = arg1.toDictionary()?["action"] as? String, action == "viewed" { - return JSValue(bool: true, in: context) - } - return JSValue(bool: false, in: context) - } + let context = JSContext()! + JSUtilities.polyfill(context) - hooks.cancelBeacon.tap { (arg1: JSValue, arg2: JSValue) -> Bool in - cancelBeaconHandler.fulfill() + let handlerExpectation = expectation(description: "Beacon Handler called") + let cancelBeaconHandler = expectation(description: "Cancel Beacon Handler called") + let buildBeaconHandler = expectation(description: "Build Beacon Handler called") + let handler = MockHandler() - if let action = arg1.toDictionary()?["action"] as? String, action == "viewed" { - return true - } - return false - } + let plugin = BeaconPlugin(plugins: []) { (beacon) in + print("Beacon: \(beacon)") + handler.handle(beacon.viewId!, beacon.data) + handlerExpectation.fulfill() + } + + plugin.context = context + plugin.setup(context: context) - let playerContext = SwiftUIPlayer.Context { context } + plugin.context = context - let player = SwiftUIPlayer( - flow: FlowData.COUNTER, - plugins: [ReferenceAssetsPlugin(), plugin], - context: playerContext - ) - ViewHosting.host(view: player) + plugin.beacon(assetBeacon: AssetBeacon( + action: BeaconAction.clicked.rawValue, + element: BeaconElement.button.rawValue, + asset: BeaconableAsset(id: "test"), + data: .string(data: "example") + )) - let viewExpectation = player.inspection.inspect(after: 1.0) { view in - _ = try view.vStack().first?.anyView().find(text: "Clicked 0 times") + guard let hooks = plugin.hooks else { + XCTFail("Hooks are not initialized") + return + } + +hooks.buildBeacon.tap { (arg1: JSValue, arg2: JSValue) -> JSValue? in + buildBeaconHandler.fulfill() + if let action = arg1.toDictionary()?["action"] as? String, action == BeaconAction.clicked.rawValue { + return JSValue(bool: true, in: context) } + return JSValue(bool: false, in: context) +} - wait(for: [handlerExpectation, cancelBeaconHandler, buildBeaconHandler,viewExpectation], timeout: 10) +hooks.cancelBeacon.tap { (arg1: JSValue, arg2: JSValue) -> Bool in + cancelBeaconHandler.fulfill() - ViewHosting.expel() + if let action = arg1.toDictionary()?["action"] as? String, action == BeaconAction.clicked.rawValue { + return true + } + return false } + wait(for: [handlerExpectation, cancelBeaconHandler, buildBeaconHandler], timeout: 10) + + + } } class MockHandler { From c357583dca382ad22b20e9f2165e3a07959502ee Mon Sep 17 00:00:00 2001 From: sakuntala_motukuri Date: Wed, 6 Nov 2024 22:23:11 -0500 Subject: [PATCH 08/11] updated BaseBeaconPluginTests.swift tests with cancelBeacon and buildBeacon --- .../beacon/ios/Sources/BaseBeaconPlugin.swift | 22 ++- .../ios/Tests/BaseBeaconPluginTests.swift | 101 +++++++++++++ .../swiftui/Sources/SwiftUIBeaconPlugin.swift | 8 +- .../ViewInspector/BeaconPluginTests.swift | 141 +++++++++--------- 4 files changed, 196 insertions(+), 76 deletions(-) diff --git a/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift b/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift index 9f7c123c7..975b18822 100644 --- a/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift +++ b/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift @@ -107,13 +107,25 @@ open class BaseBeaconPlugin: JSBasePlugin { } let callback: @convention(block) (JSValue?) -> Void = { [weak self] rawBeacon in guard - let object = rawBeacon?.toObject(), - let data = try? JSONSerialization.data(withJSONObject: object), - let beacon = try? AnyTypeDecodingContext(rawData: data) + let object = rawBeacon?.toObject() else { + print("Failed to convert rawBeacon to object") + return + } + + print("Raw beacon object: \(object)") + + do { + let data = try JSONSerialization.data(withJSONObject: object) + print("Serialized data: \(String(data: data, encoding: .utf8) ?? "nil")") + + let beacon = try AnyTypeDecodingContext(rawData: data) .inject(to: JSONDecoder()) .decode(BeaconStruct.self, from: data) - else { return } - self?.callback?(beacon) + + self?.callback?(beacon) + } catch { + print("Error during JSON serialization or deserialization: \(error)") + } } let jsCallback = JSValue(object: callback, in: context) as Any return [["callback": jsCallback, "plugins": plugins.map { $0.pluginRef }]] diff --git a/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift b/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift index 40a62f8d6..65ace815c 100644 --- a/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift +++ b/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift @@ -136,4 +136,105 @@ class BaseBeaconPluginTests: XCTestCase { )) wait(for: [expectation], timeout: 2) } + + func testCancelSpecificBeaconsUsingHooks() { + let context = JSContext()! + JSUtilities.polyfill(context) + + let cancelBeaconHandler = expectation(description: "Cancel Beacon Handler called") + let handlerExpectation = expectation(description: "Handler called") + handlerExpectation.isInverted = true + + let handler = MockHandler() + + let plugin = BaseBeaconPlugin(plugins: []) { (beacon) in + if handler.calls.isEmpty { + handler.handle(beacon.viewId!, beacon.data) + handlerExpectation.fulfill() + } + } + + plugin.context = context + plugin.setup(context: context) + + plugin.beacon(assetBeacon: AssetBeacon( + action: BeaconAction.clicked.rawValue, + element: BeaconElement.button.rawValue, + asset: BeaconableAsset(id: "test"), + data: .string(data: "example") + )) + + guard let hooks = plugin.hooks else { + XCTFail("Hooks are not initialized") + return + } + + hooks.cancelBeacon.tap { (arg1: JSValue, arg2: JSValue) -> Bool in + cancelBeaconHandler.fulfill() + if let action = arg1.toDictionary()?["action"] as? String, action == BeaconAction.clicked.rawValue { + return true + } + return false + } + + wait(for: [handlerExpectation, cancelBeaconHandler], timeout: 10) + } + + class MockHandler { + var calls: [(String, Any?)] = [] + + func handle(_ action: String, _ data: Any? = nil) { + calls.append((action, data)) + } + + } + + func testBuildSpecificBeaconsUsingHooks() { + let context = JSContext()! + JSUtilities.polyfill(context) + let buildBeaconHandler = expectation(description: "Build Beacon Handler called") + let handlerExpectation = expectation(description: "Handler called") + let handler = MockHandler() + + let plugin = BaseBeaconPlugin(plugins: []) { (beacon) in + if handler.calls.isEmpty { + handler.handle(beacon.viewId!, beacon.data) + handlerExpectation.fulfill() + } + } + + plugin.context = context + plugin.setup(context: context) + + plugin.beacon(assetBeacon: AssetBeacon( + action: BeaconAction.clicked.rawValue, + element: BeaconElement.button.rawValue, + asset: BeaconableAsset(id: "test"), + data: .string(data: "example") + )) + + guard let hooks = plugin.hooks else { + XCTFail("Hooks are not initialized") + return + } + + hooks.buildBeacon.tap { (arg1: JSValue, arg2: JSValue) -> JSValue? in + buildBeaconHandler.fulfill() + if let actionDict = arg1.toDictionary(), + let action = actionDict["action"] as? String, + action == BeaconAction.clicked.rawValue { + handler.handle(action, actionDict) + handlerExpectation.fulfill() // Fulfill the handler expectation + return JSValue(object: actionDict, in: arg1.context) + } + return nil + } + + wait(for: [handlerExpectation, buildBeaconHandler], timeout: 10) + + // Assert that the modified beacon data is received in the handler + XCTAssertEqual(handler.calls.count, 1) + + } + } diff --git a/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift b/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift index cd87995a2..8de65498c 100644 --- a/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift +++ b/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift @@ -24,7 +24,7 @@ open class BeaconPlugin: BaseBeaconPlugin self.callback = onBeacon self.plugins = plugins } - + open func apply

(player: P) where P: HeadlessPlayer { guard let player = player as? SwiftUIPlayer else { return } let beacon = self.beacon(assetBeacon:) @@ -39,7 +39,7 @@ open class BeaconPlugin: BaseBeaconPlugin */ public class BeaconContext: ObservableObject { private let beaconFn: (AssetBeacon) -> Void - + /** Constructs a BeaconContext - parameters: @@ -48,7 +48,7 @@ public class BeaconContext: ObservableObject { public init(_ beacon: @escaping (AssetBeacon) -> Void) { self.beaconFn = beacon } - + /** Sends a beacon through the JavaScript beacon plugin - parameters: @@ -75,7 +75,7 @@ public class BeaconContext: ObservableObject { ) ) } - + /** Sends a beacon through the JavaScript beacon plugin - parameters: diff --git a/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift b/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift index 42d6e4f5f..8d6d630c1 100644 --- a/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift +++ b/plugins/beacon/swiftui/ViewInspector/BeaconPluginTests.swift @@ -10,7 +10,6 @@ import Foundation import XCTest import SwiftUI import ViewInspector -import JavaScriptCore @testable import PlayerUI @testable import PlayerUIInternalTestUtilities @testable import PlayerUISwiftUI @@ -18,78 +17,86 @@ import JavaScriptCore @testable import PlayerUIBaseBeaconPlugin @testable import PlayerUIBeaconPlugin -extension Inspection: InspectionEmissary { } - class BeaconPluginTests: XCTestCase { override func setUp() { XCUIApplication().terminate() } - - - func testBuildCancelsSpecificBeaconsUsingHooks() { - - - let context = JSContext()! - JSUtilities.polyfill(context) - - let handlerExpectation = expectation(description: "Beacon Handler called") - let cancelBeaconHandler = expectation(description: "Cancel Beacon Handler called") - let buildBeaconHandler = expectation(description: "Build Beacon Handler called") - let handler = MockHandler() - - let plugin = BeaconPlugin(plugins: []) { (beacon) in - print("Beacon: \(beacon)") - handler.handle(beacon.viewId!, beacon.data) - handlerExpectation.fulfill() - } - - plugin.context = context - plugin.setup(context: context) - - plugin.context = context - - plugin.beacon(assetBeacon: AssetBeacon( - action: BeaconAction.clicked.rawValue, - element: BeaconElement.button.rawValue, - asset: BeaconableAsset(id: "test"), - data: .string(data: "example") - )) - - guard let hooks = plugin.hooks else { - XCTFail("Hooks are not initialized") - return - } - -hooks.buildBeacon.tap { (arg1: JSValue, arg2: JSValue) -> JSValue? in - buildBeaconHandler.fulfill() - if let action = arg1.toDictionary()?["action"] as? String, action == BeaconAction.clicked.rawValue { - return JSValue(bool: true, in: context) - } - return JSValue(bool: false, in: context) -} - -hooks.cancelBeacon.tap { (arg1: JSValue, arg2: JSValue) -> Bool in - cancelBeaconHandler.fulfill() - - if let action = arg1.toDictionary()?["action"] as? String, action == BeaconAction.clicked.rawValue { - return true - } - return false -} - - wait(for: [handlerExpectation, cancelBeaconHandler, buildBeaconHandler], timeout: 10) - - - } -} - -class MockHandler { - var calls: [(String, Any?)] = [] + // func testContextAttachment() throws { + // let player = SwiftUIPlayer(flow: "", plugins: [BeaconPlugin { _ in}]) + // + // guard let view: AnyView = player.hooks?.view.call(AnyView(TestButton())) else { + // return XCTFail("no view returned from hook") + // } + // + // // Should be wrapped in 2 anyviews, one for playercontrollers, one for beaconcontext + // _ = try view.inspect().anyView().anyView().view(TestButton.self) + // } + // func testBeaconContext() throws { + // let expect = expectation(description: "Beacon Called") + // let beacon: (AssetBeacon) -> Void = { (beaconObj: AssetBeacon) in + // guard + // beaconObj.action == "clicked", + // beaconObj.element == "button", + // beaconObj.asset.id == "test" + // else { return XCTFail("incorrect beacon information") } + // expect.fulfill() + // } + // + // let context = BeaconContext(beacon) + // var tree = TestButton() + // + // let exp = tree.on(\.didAppear) { view in + // try view.button().tap() + // } + // + // ViewHosting.host(view: tree.environment(\.beaconContext, context)) + // + // wait(for: [exp, expect], timeout: 10) + // } + // + // func testBeaconContextWithMetaData() throws { + // let expect = expectation(description: "Beacon Called") + // let beacon: (AssetBeacon) -> Void = { (beaconObj: AssetBeacon) in + // guard + // beaconObj.action == "clicked", + // beaconObj.element == "button", + // beaconObj.asset.id == "test", + // case .dictionary(let data) = beaconObj.asset.metaData?.beacon, + // data["field"] == "value" + // else { return XCTFail("incorrect beacon information") } + // expect.fulfill() + // } + // + // let context = BeaconContext(beacon) + // let data = MetaData(beacon: .dictionary(data: ["field": "value"])) + // var tree = TestButton(metaData: data) + // + // let exp = tree.on(\.didAppear) { view in + // try view.button().tap() + // } + // + // ViewHosting.host(view: tree.environment(\.beaconContext, context)) + // + // wait(for: [exp, expect], timeout: 10) + // } - func handle(_ action: String, _ data: Any? = nil) { - calls.append((action, data)) + func testSendsViewBeacon() { + let beaconed = expectation(description: "View beacon called") + let plugin = BeaconPlugin(plugins: []) { (beacon) in + guard beacon.action == "viewed" else { return } + beaconed.fulfill() + } + + let player = SwiftUIPlayer( + flow: FlowData.COUNTER, + plugins: [ReferenceAssetsPlugin(), plugin] + ) + ViewHosting.host(view: player) + + wait(for: [beaconed], timeout: 10) + + ViewHosting.expel() } - } struct TestButton: View { From 5bea09589c902d3da8c5f3c906429b9bd8667fe4 Mon Sep 17 00:00:00 2001 From: sakuntala_motukuri Date: Thu, 7 Nov 2024 16:30:34 -0500 Subject: [PATCH 09/11] Fixed feedback --- .../beacon/ios/Sources/BaseBeaconPlugin.swift | 22 +---- .../ios/Tests/BaseBeaconPluginTests.swift | 93 ++++++++++--------- 2 files changed, 54 insertions(+), 61 deletions(-) diff --git a/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift b/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift index 975b18822..9f7c123c7 100644 --- a/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift +++ b/plugins/beacon/ios/Sources/BaseBeaconPlugin.swift @@ -107,25 +107,13 @@ open class BaseBeaconPlugin: JSBasePlugin { } let callback: @convention(block) (JSValue?) -> Void = { [weak self] rawBeacon in guard - let object = rawBeacon?.toObject() else { - print("Failed to convert rawBeacon to object") - return - } - - print("Raw beacon object: \(object)") - - do { - let data = try JSONSerialization.data(withJSONObject: object) - print("Serialized data: \(String(data: data, encoding: .utf8) ?? "nil")") - - let beacon = try AnyTypeDecodingContext(rawData: data) + let object = rawBeacon?.toObject(), + let data = try? JSONSerialization.data(withJSONObject: object), + let beacon = try? AnyTypeDecodingContext(rawData: data) .inject(to: JSONDecoder()) .decode(BeaconStruct.self, from: data) - - self?.callback?(beacon) - } catch { - print("Error during JSON serialization or deserialization: \(error)") - } + else { return } + self?.callback?(beacon) } let jsCallback = JSValue(object: callback, in: context) as Any return [["callback": jsCallback, "plugins": plugins.map { $0.pluginRef }]] diff --git a/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift b/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift index 65ace815c..bf06841b9 100644 --- a/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift +++ b/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift @@ -145,25 +145,21 @@ class BaseBeaconPluginTests: XCTestCase { let handlerExpectation = expectation(description: "Handler called") handlerExpectation.isInverted = true - let handler = MockHandler() - - let plugin = BaseBeaconPlugin(plugins: []) { (beacon) in - if handler.calls.isEmpty { - handler.handle(beacon.viewId!, beacon.data) - handlerExpectation.fulfill() + let plugin = BaseBeaconPlugin( onBeacon: { (beacon) in + XCTAssertEqual(beacon.assetId, "test") + XCTAssertEqual(beacon.element, BeaconElement.button.rawValue) + switch beacon.data { + case .string(let string): + XCTAssertEqual(string, "example") + default: + XCTFail("beacon data was not a string") } - } + handlerExpectation.fulfill() + }) plugin.context = context plugin.setup(context: context) - plugin.beacon(assetBeacon: AssetBeacon( - action: BeaconAction.clicked.rawValue, - element: BeaconElement.button.rawValue, - asset: BeaconableAsset(id: "test"), - data: .string(data: "example") - )) - guard let hooks = plugin.hooks else { XCTFail("Hooks are not initialized") return @@ -177,42 +173,46 @@ class BaseBeaconPluginTests: XCTestCase { return false } - wait(for: [handlerExpectation, cancelBeaconHandler], timeout: 10) - } - - class MockHandler { - var calls: [(String, Any?)] = [] - - func handle(_ action: String, _ data: Any? = nil) { - calls.append((action, data)) - } + plugin.beacon(assetBeacon: AssetBeacon( + action: BeaconAction.clicked.rawValue, + element: BeaconElement.button.rawValue, + asset: BeaconableAsset(id: "test"), + data: .string(data: "example") + )) + wait(for: [handlerExpectation, cancelBeaconHandler], timeout: 10) } func testBuildSpecificBeaconsUsingHooks() { let context = JSContext()! JSUtilities.polyfill(context) let buildBeaconHandler = expectation(description: "Build Beacon Handler called") - let handlerExpectation = expectation(description: "Handler called") - let handler = MockHandler() + let handlerExpectation1 = expectation(description: "Handler called once") + let handlerExpectation2 = expectation(description: "Handler called twice") + handlerExpectation2.isInverted = true + + var handlerCallCount = 0 - let plugin = BaseBeaconPlugin(plugins: []) { (beacon) in - if handler.calls.isEmpty { - handler.handle(beacon.viewId!, beacon.data) - handlerExpectation.fulfill() + let plugin = BaseBeaconPlugin(onBeacon: { (beacon) in + handlerCallCount += 1 + XCTAssertEqual(beacon.assetId, "test") + XCTAssertEqual(beacon.element, BeaconElement.button.rawValue) + switch beacon.data { + case .string(let string): + XCTAssertEqual(string, "modified example") + default: + XCTFail("beacon data was not a string") } - } + if handlerCallCount == 1 { + handlerExpectation1.fulfill() + } else if handlerCallCount > 1 { + handlerExpectation2.fulfill() + } + }) plugin.context = context plugin.setup(context: context) - plugin.beacon(assetBeacon: AssetBeacon( - action: BeaconAction.clicked.rawValue, - element: BeaconElement.button.rawValue, - asset: BeaconableAsset(id: "test"), - data: .string(data: "example") - )) - guard let hooks = plugin.hooks else { XCTFail("Hooks are not initialized") return @@ -220,21 +220,26 @@ class BaseBeaconPluginTests: XCTestCase { hooks.buildBeacon.tap { (arg1: JSValue, arg2: JSValue) -> JSValue? in buildBeaconHandler.fulfill() - if let actionDict = arg1.toDictionary(), + if var actionDict = arg1.toDictionary() as? [String: Any], let action = actionDict["action"] as? String, action == BeaconAction.clicked.rawValue { - handler.handle(action, actionDict) - handlerExpectation.fulfill() // Fulfill the handler expectation + actionDict["data"] = "modified example" return JSValue(object: actionDict, in: arg1.context) } return nil } - wait(for: [handlerExpectation, buildBeaconHandler], timeout: 10) + plugin.beacon(assetBeacon: AssetBeacon( + action: BeaconAction.clicked.rawValue, + element: BeaconElement.button.rawValue, + asset: BeaconableAsset(id: "test"), + data: .string(data: "example") + )) - // Assert that the modified beacon data is received in the handler - XCTAssertEqual(handler.calls.count, 1) + wait(for: [handlerExpectation1, buildBeaconHandler], timeout: 10) + wait(for: [handlerExpectation2], timeout: 1) + // Assert that the handler was called exactly once + XCTAssertEqual(handlerCallCount, 1) } - } From 040c89fd233b11048a017972762e6d4b24fab152 Mon Sep 17 00:00:00 2001 From: sakuntala_motukuri Date: Thu, 7 Nov 2024 16:48:47 -0500 Subject: [PATCH 10/11] updated expectedfulfillmentcount instead of multiple expectations --- .../ios/Tests/BaseBeaconPluginTests.swift | 86 +++++++++---------- 1 file changed, 40 insertions(+), 46 deletions(-) diff --git a/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift b/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift index bf06841b9..cf97b9f1e 100644 --- a/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift +++ b/plugins/beacon/ios/Tests/BaseBeaconPluginTests.swift @@ -14,18 +14,18 @@ import JavaScriptCore class BPPlugin: JSBasePlugin { override open func setup(context: JSContext) { - + } } class BaseBeaconPluginTests: XCTestCase { func testBundleLoadsSymbols() { let context = JSContext()! - + let plugin = BaseBeaconPlugin() { _ in } - + plugin.context = context - + // as accessed on main from beacon-plugin.prod.js XCTAssertFalse(context.objectForKeyedSubscript("BeaconPlugin").objectForKeyedSubscript("BeaconPlugin").isUndefined) XCTAssertFalse(context.objectForKeyedSubscript("BeaconPlugin").objectForKeyedSubscript("BeaconPluginSymbol").isUndefined) @@ -34,30 +34,30 @@ class BaseBeaconPluginTests: XCTestCase { let context = JSContext()! JSUtilities.polyfill(context) let bpp = BPPlugin(fileName: "", pluginName: "") - + let plugin = BaseBeaconPlugin(plugins: [bpp]) { _ in } plugin.context = context - + XCTAssertNotNil(bpp.context) } - + func testBeaconFunction() { let context = JSContext()! JSUtilities.polyfill(context) - + let beaconed = expectation(description: "Beacon sent") - + let plugin = BaseBeaconPlugin(plugins: []) { _ in beaconed.fulfill() } plugin.context = context - + plugin.beacon(assetBeacon: AssetBeacon(action: "action", element: "element", asset: BeaconableAsset(id: "id"))) wait(for: [beaconed], timeout: 1) } - + func testBeaconPluginStringData() { let context = JSContext()! JSUtilities.polyfill(context) - + let expectation = XCTestExpectation(description: "beacon callback called") let plugin = BaseBeaconPlugin( onBeacon: { (beacon) in XCTAssertEqual(beacon.assetId, "test") @@ -71,7 +71,7 @@ class BaseBeaconPluginTests: XCTestCase { expectation.fulfill() }) plugin.context = context - + plugin.beacon(assetBeacon: AssetBeacon( action: BeaconAction.clicked.rawValue, element: BeaconElement.button.rawValue, @@ -80,11 +80,11 @@ class BaseBeaconPluginTests: XCTestCase { )) wait(for: [expectation], timeout: 2) } - + func testBeaconPluginDictionaryData() { let context = JSContext()! JSUtilities.polyfill(context) - + let expectation = XCTestExpectation(description: "beacon callback called") let plugin = BaseBeaconPlugin(onBeacon: { (beacon) in XCTAssertEqual(beacon.assetId, "test") @@ -98,7 +98,7 @@ class BaseBeaconPluginTests: XCTestCase { expectation.fulfill() }) plugin.context = context - + plugin.beacon(assetBeacon: AssetBeacon( action: BeaconAction.clicked.rawValue, element: BeaconElement.button.rawValue, @@ -107,11 +107,11 @@ class BaseBeaconPluginTests: XCTestCase { )) wait(for: [expectation], timeout: 2) } - + func testBeaconPluginAnyDictionaryData() { let context = JSContext()! JSUtilities.polyfill(context) - + let expectation = XCTestExpectation(description: "beacon callback called") let plugin = BaseBeaconPlugin(onBeacon: { (beacon) in XCTAssertEqual(beacon.assetId, "test") @@ -127,7 +127,7 @@ class BaseBeaconPluginTests: XCTestCase { expectation.fulfill() }) plugin.context = context - + plugin.beacon(assetBeacon: AssetBeacon( action: BeaconAction.clicked.rawValue, element: BeaconElement.button.rawValue, @@ -136,15 +136,15 @@ class BaseBeaconPluginTests: XCTestCase { )) wait(for: [expectation], timeout: 2) } - + func testCancelSpecificBeaconsUsingHooks() { let context = JSContext()! JSUtilities.polyfill(context) - + let cancelBeaconHandler = expectation(description: "Cancel Beacon Handler called") let handlerExpectation = expectation(description: "Handler called") handlerExpectation.isInverted = true - + let plugin = BaseBeaconPlugin( onBeacon: { (beacon) in XCTAssertEqual(beacon.assetId, "test") XCTAssertEqual(beacon.element, BeaconElement.button.rawValue) @@ -156,15 +156,15 @@ class BaseBeaconPluginTests: XCTestCase { } handlerExpectation.fulfill() }) - + plugin.context = context plugin.setup(context: context) - + guard let hooks = plugin.hooks else { XCTFail("Hooks are not initialized") return } - + hooks.cancelBeacon.tap { (arg1: JSValue, arg2: JSValue) -> Bool in cancelBeaconHandler.fulfill() if let action = arg1.toDictionary()?["action"] as? String, action == BeaconAction.clicked.rawValue { @@ -172,27 +172,26 @@ class BaseBeaconPluginTests: XCTestCase { } return false } - + plugin.beacon(assetBeacon: AssetBeacon( action: BeaconAction.clicked.rawValue, element: BeaconElement.button.rawValue, asset: BeaconableAsset(id: "test"), data: .string(data: "example") )) - + wait(for: [handlerExpectation, cancelBeaconHandler], timeout: 10) } - + func testBuildSpecificBeaconsUsingHooks() { let context = JSContext()! JSUtilities.polyfill(context) let buildBeaconHandler = expectation(description: "Build Beacon Handler called") - let handlerExpectation1 = expectation(description: "Handler called once") - let handlerExpectation2 = expectation(description: "Handler called twice") - handlerExpectation2.isInverted = true - + let handlerExpectation = expectation(description: "Handler called") + handlerExpectation.expectedFulfillmentCount = 1 + var handlerCallCount = 0 - + let plugin = BaseBeaconPlugin(onBeacon: { (beacon) in handlerCallCount += 1 XCTAssertEqual(beacon.assetId, "test") @@ -203,21 +202,17 @@ class BaseBeaconPluginTests: XCTestCase { default: XCTFail("beacon data was not a string") } - if handlerCallCount == 1 { - handlerExpectation1.fulfill() - } else if handlerCallCount > 1 { - handlerExpectation2.fulfill() - } + handlerExpectation.fulfill() }) - + plugin.context = context plugin.setup(context: context) - + guard let hooks = plugin.hooks else { XCTFail("Hooks are not initialized") return } - + hooks.buildBeacon.tap { (arg1: JSValue, arg2: JSValue) -> JSValue? in buildBeaconHandler.fulfill() if var actionDict = arg1.toDictionary() as? [String: Any], @@ -228,17 +223,16 @@ class BaseBeaconPluginTests: XCTestCase { } return nil } - + plugin.beacon(assetBeacon: AssetBeacon( action: BeaconAction.clicked.rawValue, element: BeaconElement.button.rawValue, asset: BeaconableAsset(id: "test"), data: .string(data: "example") )) - - wait(for: [handlerExpectation1, buildBeaconHandler], timeout: 10) - wait(for: [handlerExpectation2], timeout: 1) - + + wait(for: [handlerExpectation, buildBeaconHandler], timeout: 10) + // Assert that the handler was called exactly once XCTAssertEqual(handlerCallCount, 1) } From e0f317a1e041a6b5d1657bb6a571165929130e9a Mon Sep 17 00:00:00 2001 From: sakuntala_motukuri Date: Fri, 8 Nov 2024 17:34:32 -0500 Subject: [PATCH 11/11] reverted deleted comments --- .../swiftui/Sources/SwiftUIBeaconPlugin.swift | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift b/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift index 8de65498c..90e1e27dc 100644 --- a/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift +++ b/plugins/beacon/swiftui/Sources/SwiftUIBeaconPlugin.swift @@ -19,12 +19,18 @@ import PlayerUIBaseBeaconPlugin Plugin used by `SwiftUIPlayer` for beaconing in a uniform format between platforms */ open class BeaconPlugin: BaseBeaconPlugin, NativePlugin { + /** + Constructs a BeaconPlugin + - parameters: + - context: The context to load the plugin into + - onBeacon: A callback to receive beacon events + */ public convenience init(plugins: [JSBasePlugin] = [], onBeacon: ((BeaconStruct) -> Void)?) { self.init(fileName: "BeaconPlugin.native", pluginName: "BeaconPlugin.BeaconPlugin") self.callback = onBeacon self.plugins = plugins } - + open func apply

(player: P) where P: HeadlessPlayer { guard let player = player as? SwiftUIPlayer else { return } let beacon = self.beacon(assetBeacon:) @@ -39,7 +45,7 @@ open class BeaconPlugin: BaseBeaconPlugin */ public class BeaconContext: ObservableObject { private let beaconFn: (AssetBeacon) -> Void - + /** Constructs a BeaconContext - parameters: @@ -48,7 +54,7 @@ public class BeaconContext: ObservableObject { public init(_ beacon: @escaping (AssetBeacon) -> Void) { self.beaconFn = beacon } - + /** Sends a beacon through the JavaScript beacon plugin - parameters: @@ -75,7 +81,7 @@ public class BeaconContext: ObservableObject { ) ) } - + /** Sends a beacon through the JavaScript beacon plugin - parameters: