// // AnalyticsGeneratorTests.swift // // // Created by Thibaut Schmitt on 06/09/2022. // // CPD-OFF import Foundation import XCTest import ToolCore @testable import ResgenSwift final class AnalyticsGeneratorTests: XCTestCase { private func getAnalyticsDefinition( id: String, path: String = "", action: String = "", category: String = "", name: String, type: AnalyticsDefinition.TagType, tags: [String] ) -> AnalyticsDefinition { let definition = AnalyticsDefinition(id: id, name: name, type: type) definition.tags = tags definition.path = path definition.action = action definition.category = category return definition } private func protocolString(visibility: ExtensionVisibility) -> String { """ // MARK: - Protocol \(visibility) protocol AnalyticsManagerProtocol { func logScreen( name: String, path: String, params: [String: Any]? ) func logEvent( name: String, action: String, category: String, params: [String: Any]? ) func setEnable(_ enable: Bool) } """ } private func firebaseString() -> String { """ // MARK: - Firebase class FirebaseAnalyticsManager: AnalyticsManagerProtocol { // MARK: - Methods func logScreen( name: String, path: String, params: [String: Any]? ) { var parameters = [ AnalyticsParameterScreenName: name as NSObject ] if path.isEmpty == false { parameters["path"] = path + "/iOS" as NSObject } if let supplementaryParameters = params { for (newKey, newValue) in supplementaryParameters { if parameters.contains(where: { (key: String, value: NSObject) in key == newKey }) { continue } parameters[newKey] = newValue as? NSObject } } Analytics.logEvent( AnalyticsEventScreenView, parameters: parameters ) } func logEvent( name: String, action: String, category: String, params: [String: Any]? ) { var parameters: [String:NSObject] = [ AnalyticsParameterItemName: name.replacingOccurrences(of: " ", with: "_") as NSObject ] if category.isEmpty == false { parameters["AnalyticsParameterItemCategory"] = category as NSObject } if action.isEmpty == false { parameters["action"] = action as NSObject } if let supplementaryParameters = params { for (newKey, newValue) in supplementaryParameters { if parameters.contains(where: { (key: String, value: NSObject) in key == newKey }) { continue } parameters[newKey] = newValue as? NSObject } } Analytics.logEvent( AnalyticsEventSelectContent, parameters: parameters ) } func setEnable(_ enable: Bool) { Analytics.setAnalyticsCollectionEnabled(enable) } } """ } private func matomoString() -> String { """ // MARK: - Matomo class MatomoAnalyticsManager: AnalyticsManagerProtocol { // MARK: - Properties private var tracker: MatomoTracker // MARK: - Init init(siteId: String, url: String) { debugPrint("[Matomo service] Server URL: \\(url)") debugPrint("[Matomo service] Site ID: \\(siteId)") tracker = MatomoTracker( siteId: siteId, baseURL: URL(string: url)! ) #if DEBUG tracker.dispatchInterval = 5 #endif #if DEBUG tracker.logger = DefaultLogger(minLevel: .verbose) #endif debugPrint("[Matomo service] Configured with content base: \\(tracker.contentBase?.absoluteString ?? "-")") debugPrint("[Matomo service] Opt out: \\(tracker.isOptedOut)") } // MARK: - Methods func logScreen( name: String, path: String, params: [String: Any]? ) { guard let trackerUrl = tracker.contentBase?.absoluteString else { return } let urlString = URL(string: "\\(trackerUrl)" + "/" + "\\(path)" + "iOS") tracker.track( view: [name], url: urlString ) } func logEvent( name: String, action: String, category: String, params: [String: Any]? ) { tracker.track( eventWithCategory: category, action: action, name: name, number: nil, url: nil ) } func setEnable(_ enable: Bool) { tracker.isOptedOut = !enable } } """ } func testGeneratedExtensionContentFirebase() { // Given let sectionOne = AnalyticsCategory(id: "section_one") sectionOne.definitions = [ getAnalyticsDefinition(id: "s1_def_one", name: "s1 def one", type: .screen, tags: ["ios", "iosonly"]), getAnalyticsDefinition(id: "s1_def_two", name: "s1 def two", type: .event, tags: ["ios", "iosonly"]), ] let sectionTwo = AnalyticsCategory(id: "section_two") sectionTwo.definitions = [ getAnalyticsDefinition(id: "s2_def_one", name: "s2 def one", type: .screen, tags: ["ios","iosonly"]), getAnalyticsDefinition(id: "s2_def_two", name: "s2 def two", type: .event, tags: ["droid","droidonly"]), ] let sectionThree = AnalyticsCategory(id: "section_three") sectionThree.definitions = [ getAnalyticsDefinition(id: "s3_def_one", name: "s3 def one", type: .screen, tags: ["droid","droidonly"]), getAnalyticsDefinition(id: "s3_def_two", name: "s3 def two", type: .event, tags: ["droid","droidonly"]), ] // When let extensionContent = AnalyticsGenerator.getExtensionContent( targets: [TrackerType.firebase], sections: [sectionOne, sectionTwo, sectionThree], tags: ["ios", "iosonly"], staticVar: false, visibility: .public ) // Expect Analytics let expect = """ // Generated by ResgenSwift.Analytics \(ResgenSwiftVersion) import Foundation import FirebaseAnalytics \(protocolString(visibility: .public)) \(firebaseString()) // MARK: - Traker Type public enum TrackerType: CaseIterable { case firebase } // MARK: - Manager public class AnalyticsManager { public static var shared = AnalyticsManager() private init() {} // MARK: - Properties var managers: [TrackerType: AnalyticsManagerProtocol] = [:] private var isEnabled: Bool { if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" { false } else { true } } // MARK: - Enable Methods private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) { managers.forEach { (key, value) in if analytics.contains(where: { type in type == key }) { value.setEnable(enable) } } } public func enableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { setAnalytics(enable: true, analytics) } public func disableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { setAnalytics(enable: false, analytics) } public func configure() { managers[TrackerType.firebase] = FirebaseAnalyticsManager() } // MARK: - Private Log Methods private func logScreen( name: String, path: String, params: [String: Any]? ) { guard isEnabled else { return } managers.values.forEach { manager in manager.logScreen( name: name, path: path, params: params ) } } private func logEvent( name: String, action: String, category: String, params: [String: Any]? ) { guard isEnabled else { return } managers.values.forEach { manager in manager.logEvent( name: name, action: action, category: category, params: params ) } } // MARK: - section_one public func logScreenS1DefOne() { logScreen( name: "s1 def one", path: "", params: nil ) } public func logEventS1DefTwo() { logEvent( name: "s1 def two", action: "", category: "", params: nil ) } // MARK: - section_two public func logScreenS2DefOne() { logScreen( name: "s2 def one", path: "", params: nil ) } } """ if extensionContent != expect { print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect)) } XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest()) } func testGeneratedExtensionContentMatomo() { // Given let sectionOne = AnalyticsCategory(id: "section_one") sectionOne.definitions = [ getAnalyticsDefinition(id: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: .screen, tags: ["ios", "iosonly"]), getAnalyticsDefinition(id: "s1_def_two", action: "test", category: "test", name: "s1 def two", type: .event, tags: ["ios", "iosonly"]), ] let sectionTwo = AnalyticsCategory(id: "section_two") sectionTwo.definitions = [ getAnalyticsDefinition(id: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: .screen, tags: ["ios","iosonly"]), getAnalyticsDefinition(id: "s2_def_two", action: "test", category: "test", name: "s2 def two", type: .event, tags: ["droid","droidonly"]), ] let sectionThree = AnalyticsCategory(id: "section_three") sectionThree.definitions = [ getAnalyticsDefinition(id: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: .screen, tags: ["droid","droidonly"]), getAnalyticsDefinition(id: "s3_def_two", action: "test", category: "test", name: "s3 def two", type: .event, tags: ["droid","droidonly"]), ] // When let extensionContent = AnalyticsGenerator.getExtensionContent( targets: [TrackerType.matomo], sections: [sectionOne, sectionTwo, sectionThree], tags: ["ios", "iosonly"], staticVar: false, visibility: .package ) // Expect Analytics let expect = """ // Generated by ResgenSwift.Analytics \(ResgenSwiftVersion) import Foundation import MatomoTracker \(protocolString(visibility: .package)) \(matomoString()) // MARK: - Traker Type package enum TrackerType: CaseIterable { case matomo } // MARK: - Manager package class AnalyticsManager { package static var shared = AnalyticsManager() private init() {} // MARK: - Properties var managers: [TrackerType: AnalyticsManagerProtocol] = [:] private var isEnabled: Bool { if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" { false } else { true } } // MARK: - Enable Methods private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) { managers.forEach { (key, value) in if analytics.contains(where: { type in type == key }) { value.setEnable(enable) } } } package func enableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { setAnalytics(enable: true, analytics) } package func disableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { setAnalytics(enable: false, analytics) } package func configure(siteId: String, url: String) { managers[TrackerType.matomo] = MatomoAnalyticsManager( siteId: siteId, url: url ) } // MARK: - Private Log Methods private func logScreen( name: String, path: String, params: [String: Any]? ) { guard isEnabled else { return } managers.values.forEach { manager in manager.logScreen( name: name, path: path, params: params ) } } private func logEvent( name: String, action: String, category: String, params: [String: Any]? ) { guard isEnabled else { return } managers.values.forEach { manager in manager.logEvent( name: name, action: action, category: category, params: params ) } } // MARK: - section_one package func logScreenS1DefOne() { logScreen( name: "s1 def one", path: "s1_def_one/", params: nil ) } package func logEventS1DefTwo() { logEvent( name: "s1 def two", action: "test", category: "test", params: nil ) } // MARK: - section_two package func logScreenS2DefOne() { logScreen( name: "s2 def one", path: "s2_def_one/", params: nil ) } } """ if extensionContent != expect { print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect)) } XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest()) } func testGeneratedExtensionContentMatomoAndFirebase() { // Given let sectionOne = AnalyticsCategory(id: "section_one") sectionOne.definitions = [ getAnalyticsDefinition(id: "s1_def_one", path: "s1_def_one/", name: "s1 def one", type: .screen, tags: ["ios", "iosonly"]), getAnalyticsDefinition(id: "s1_def_two", action: "test", category: "test", name: "s1 def two", type: .event, tags: ["ios", "iosonly"]), ] let sectionTwo = AnalyticsCategory(id: "section_two") sectionTwo.definitions = [ getAnalyticsDefinition(id: "s2_def_one", path: "s2_def_one/", name: "s2 def one", type: .screen, tags: ["ios","iosonly"]), getAnalyticsDefinition(id: "s2_def_two", action: "test", category: "test", name: "s2 def two", type: .event, tags: ["droid","droidonly"]), ] let sectionThree = AnalyticsCategory(id: "section_three") sectionThree.definitions = [ getAnalyticsDefinition(id: "s3_def_one", path: "s3_def_one/", name: "s3 def one", type: .screen, tags: ["droid","droidonly"]), getAnalyticsDefinition(id: "s3_def_two", action: "test", category: "test", name: "s3 def two", type: .event, tags: ["droid","droidonly"]), ] // When let extensionContent = AnalyticsGenerator.getExtensionContent( targets: [TrackerType.matomo, TrackerType.firebase], sections: [sectionOne, sectionTwo, sectionThree], tags: ["ios", "iosonly"], staticVar: false, visibility: .internal ) // Expect Analytics let expect = """ // Generated by ResgenSwift.Analytics \(ResgenSwiftVersion) import Foundation import MatomoTracker import FirebaseAnalytics \(protocolString(visibility: .internal)) \(matomoString()) \(firebaseString()) // MARK: - Traker Type internal enum TrackerType: CaseIterable { case matomo case firebase } // MARK: - Manager internal class AnalyticsManager { internal static var shared = AnalyticsManager() private init() {} // MARK: - Properties var managers: [TrackerType: AnalyticsManagerProtocol] = [:] private var isEnabled: Bool { if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" { false } else { true } } // MARK: - Enable Methods private func setAnalytics(enable: Bool, _ analytics: [TrackerType]) { managers.forEach { (key, value) in if analytics.contains(where: { type in type == key }) { value.setEnable(enable) } } } internal func enableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { setAnalytics(enable: true, analytics) } internal func disableAnalytics(_ analytics: [TrackerType] = TrackerType.allCases) { setAnalytics(enable: false, analytics) } internal func configure(siteId: String, url: String) { managers[TrackerType.matomo] = MatomoAnalyticsManager( siteId: siteId, url: url ) managers[TrackerType.firebase] = FirebaseAnalyticsManager() } // MARK: - Private Log Methods private func logScreen( name: String, path: String, params: [String: Any]? ) { guard isEnabled else { return } managers.values.forEach { manager in manager.logScreen( name: name, path: path, params: params ) } } private func logEvent( name: String, action: String, category: String, params: [String: Any]? ) { guard isEnabled else { return } managers.values.forEach { manager in manager.logEvent( name: name, action: action, category: category, params: params ) } } // MARK: - section_one internal func logScreenS1DefOne() { logScreen( name: "s1 def one", path: "s1_def_one/", params: nil ) } internal func logEventS1DefTwo() { logEvent( name: "s1 def two", action: "test", category: "test", params: nil ) } // MARK: - section_two internal func logScreenS2DefOne() { logScreen( name: "s2 def one", path: "s2_def_one/", params: nil ) } } """ if extensionContent != expect { print(prettyFirstDifferenceBetweenStrings(s1: extensionContent, s2: expect)) } XCTAssertEqual(extensionContent.adaptForXCTest(), expect.adaptForXCTest()) } } // CPD-ON