From 919df0a23e60718ee70be0bd5a48a59e26ff3eaf Mon Sep 17 00:00:00 2001 From: Mark Pospesel Date: Wed, 8 Mar 2023 17:41:49 +0100 Subject: [PATCH] [Issue-7] Add Event Factory --- .../YAnalytics/Protocol/AnalyticsEngine.swift | 15 +++++++ .../Protocol/AnalyticsEventFactory.swift | 18 ++++++++ .../YAnalytics/Protocol/EventFactory.swift | 25 +++++++++++ .../Protocol/ScreenViewFactory.swift | 29 +++++++++++++ .../Protocol/UserPropertyFactory.swift | 25 +++++++++++ .../Protocols/AnalyticsEngineTests.swift | 34 +++++++++++++++ .../Protocols/EventFactoryTests.swift | 41 +++++++++++++++++++ .../Protocols/ScreenViewFactoryTests.swift | 31 ++++++++++++++ .../Protocols/UserPropertyFactoryTests.swift | 35 ++++++++++++++++ 9 files changed, 253 insertions(+) create mode 100644 Sources/YAnalytics/Protocol/AnalyticsEventFactory.swift create mode 100644 Sources/YAnalytics/Protocol/EventFactory.swift create mode 100644 Sources/YAnalytics/Protocol/ScreenViewFactory.swift create mode 100644 Sources/YAnalytics/Protocol/UserPropertyFactory.swift create mode 100644 Tests/YAnalyticsTests/Protocols/AnalyticsEngineTests.swift create mode 100644 Tests/YAnalyticsTests/Protocols/EventFactoryTests.swift create mode 100644 Tests/YAnalyticsTests/Protocols/ScreenViewFactoryTests.swift create mode 100644 Tests/YAnalyticsTests/Protocols/UserPropertyFactoryTests.swift diff --git a/Sources/YAnalytics/Protocol/AnalyticsEngine.swift b/Sources/YAnalytics/Protocol/AnalyticsEngine.swift index 0f41e60..1611892 100644 --- a/Sources/YAnalytics/Protocol/AnalyticsEngine.swift +++ b/Sources/YAnalytics/Protocol/AnalyticsEngine.swift @@ -13,4 +13,19 @@ public protocol AnalyticsEngine { /// Track an analytics event /// - Parameter event: the event to log func track(event: AnalyticsEvent) + + /// Tracks an analytics event + /// - Parameter factory: object that generates the event to log + func track(event factory: AnalyticsEventFactory) +} + +/// Default implementation of `track(factory:)` +extension AnalyticsEngine { + /// Tracks an analytics event. + /// + /// Extracts the event from the factory and tracks that. + /// - Parameter factory: object that generates the event to log + public func track(event factory: AnalyticsEventFactory) { + track(event: factory.event) + } } diff --git a/Sources/YAnalytics/Protocol/AnalyticsEventFactory.swift b/Sources/YAnalytics/Protocol/AnalyticsEventFactory.swift new file mode 100644 index 0000000..bfd2043 --- /dev/null +++ b/Sources/YAnalytics/Protocol/AnalyticsEventFactory.swift @@ -0,0 +1,18 @@ +// +// AnalyticsEventFactory.swift +// YAnalytics +// +// Created by Mark Pospesel on 3/8/23. +// Copyright © 2023 Y Media Labs. All rights reserved. +// + +import Foundation + +/// Anything that generates an analytics event. +/// +/// Three protocols, `ScreenViewFactory`, `UserPropertyFactory`, and `EventFactory`, conform to `AnalyticsEventFactory`; +/// one for each of the three cases in the `AnalyticsEvent` enum. +public protocol AnalyticsEventFactory { + /// The event + var event: AnalyticsEvent { get } +} diff --git a/Sources/YAnalytics/Protocol/EventFactory.swift b/Sources/YAnalytics/Protocol/EventFactory.swift new file mode 100644 index 0000000..aa692b4 --- /dev/null +++ b/Sources/YAnalytics/Protocol/EventFactory.swift @@ -0,0 +1,25 @@ +// +// EventFactory.swift +// YAnalytics +// +// Created by Mark Pospesel on 3/8/23. +// Copyright © 2023 Y Media Labs. All rights reserved. +// + +import Foundation + +/// Anything that generates an analytics event of type `AnalyticsEvent.event` +public protocol EventFactory: AnalyticsEventFactory { + /// Event name + var name: String { get } + /// Event parameters + var parameters: Metadata? { get } +} + +/// Default implementation of `event` property +public extension EventFactory { + /// Generates an event (of type `.event`) from `name` and `parameters` + var event: AnalyticsEvent { + .event(name: name, parameters: parameters) + } +} diff --git a/Sources/YAnalytics/Protocol/ScreenViewFactory.swift b/Sources/YAnalytics/Protocol/ScreenViewFactory.swift new file mode 100644 index 0000000..13d8068 --- /dev/null +++ b/Sources/YAnalytics/Protocol/ScreenViewFactory.swift @@ -0,0 +1,29 @@ +// +// ScreenViewFactory.swift +// YAnalytics +// +// Created by Mark Pospesel on 3/8/23. +// Copyright © 2023 Y Media Labs. All rights reserved. +// + +import Foundation + +/// Anything that generates an analytics event of type `AnalyticsEvent.screenView` +public protocol ScreenViewFactory: AnalyticsEventFactory { + /// Screen name + var screenName: String { get } +} + +/// Default implementation of `event` property +public extension ScreenViewFactory { + /// Generates an event (of type `.screenView`) from `screenName` + var event: AnalyticsEvent { + .screenView(screenName: screenName) + } +} + +/// RawRepresentable (e.g. all string-based enums) conforms to ScreenViewFactory by simply returning its raw value +extension RawRepresentable where RawValue == String { + /// URL path string + public var screenName: String { rawValue } +} diff --git a/Sources/YAnalytics/Protocol/UserPropertyFactory.swift b/Sources/YAnalytics/Protocol/UserPropertyFactory.swift new file mode 100644 index 0000000..cf62dd1 --- /dev/null +++ b/Sources/YAnalytics/Protocol/UserPropertyFactory.swift @@ -0,0 +1,25 @@ +// +// UserPropertyFactory.swift +// YAnalytics +// +// Created by Mark Pospesel on 3/8/23. +// Copyright © 2023 Y Media Labs. All rights reserved. +// + +import Foundation + +/// Anything that generates an analytics event of type `AnalyticsEvent.userProperty` +public protocol UserPropertyFactory: AnalyticsEventFactory { + /// Property name + var name: String { get } + /// Property value + var value: String { get } +} + +/// Default implementation of `event` property +public extension UserPropertyFactory { + /// Generates an event (of type `.userProperty`) from `name` and `value` + var event: AnalyticsEvent { + .userProperty(name: name, value: value) + } +} diff --git a/Tests/YAnalyticsTests/Protocols/AnalyticsEngineTests.swift b/Tests/YAnalyticsTests/Protocols/AnalyticsEngineTests.swift new file mode 100644 index 0000000..9429132 --- /dev/null +++ b/Tests/YAnalyticsTests/Protocols/AnalyticsEngineTests.swift @@ -0,0 +1,34 @@ +// +// AnalyticsEngineTests.swift +// YAnalytics +// +// Created by Mark Pospesel on 3/8/23. +// Copyright © 2023 Y Media Labs. All rights reserved. +// + +import XCTest +@testable import YAnalytics + +final class AnalyticsEngineTests: XCTestCase { + func test_trackFactory_tracksEvents() { + // Given + let engine = MockAnalyticsEngine() + + // When + MockEvent.allCases.forEach { + engine.track(event: $0) + } + + // Then + XCTAssertEqual(engine.allEvents.count, 3) + XCTAssertEqual(engine.screenViews.joined(), "abcdefxyz") + } +} + +private extension AnalyticsEngineTests { + enum MockEvent: String, CaseIterable, ScreenViewFactory { + case abc + case def + case xyz + } +} diff --git a/Tests/YAnalyticsTests/Protocols/EventFactoryTests.swift b/Tests/YAnalyticsTests/Protocols/EventFactoryTests.swift new file mode 100644 index 0000000..eda251e --- /dev/null +++ b/Tests/YAnalyticsTests/Protocols/EventFactoryTests.swift @@ -0,0 +1,41 @@ +// +// EventFactoryTests.swift +// YAnalytics +// +// Created by Mark Pospesel on 3/8/23. +// Copyright © 2023 Y Media Labs. All rights reserved. +// + +import XCTest +@testable import YAnalytics + +final class EventFactoryTests: XCTestCase { + func test_event_deliversEvent() throws { + try MockEvent.allCases.forEach { + switch $0.event { + case .event(let name, let parameters): + XCTAssertEqual(name, $0.name) + let parameters = try XCTUnwrap(parameters as? [String: String]) + let expected = try XCTUnwrap($0.parameters as? [String: String]) + XCTAssertEqual(parameters, expected) + default: + XCTFail("Unexpected event type: \($0.event)") + } + } + } +} + +private extension EventFactoryTests { + enum MockEvent: String, EventFactory, CaseIterable { + case abc + case def + + var name: String { rawValue } + var parameters: Metadata? { + [ + "value": "42\(rawValue.uppercased())", + "type": "dictionary" + ] + } + } +} diff --git a/Tests/YAnalyticsTests/Protocols/ScreenViewFactoryTests.swift b/Tests/YAnalyticsTests/Protocols/ScreenViewFactoryTests.swift new file mode 100644 index 0000000..b421b8d --- /dev/null +++ b/Tests/YAnalyticsTests/Protocols/ScreenViewFactoryTests.swift @@ -0,0 +1,31 @@ +// +// ScreenViewFactoryTests.swift +// YAnalytics +// +// Created by Mark Pospesel on 3/8/23. +// Copyright © 2023 Y Media Labs. All rights reserved. +// + +import XCTest +@testable import YAnalytics + +final class ScreenViewFactoryTests: XCTestCase { + func test_event_deliversEvent() { + MockScreenView.allCases.forEach { + switch $0.event { + case .screenView(let screenName): + XCTAssertEqual(screenName, $0.screenName) + XCTAssertEqual(screenName, $0.rawValue) + default: + XCTFail("Unexpected event type: \($0.event)") + } + } + } +} + +private extension ScreenViewFactoryTests { + enum MockScreenView: String, ScreenViewFactory, CaseIterable { + case abc + case def + } +} diff --git a/Tests/YAnalyticsTests/Protocols/UserPropertyFactoryTests.swift b/Tests/YAnalyticsTests/Protocols/UserPropertyFactoryTests.swift new file mode 100644 index 0000000..8823a31 --- /dev/null +++ b/Tests/YAnalyticsTests/Protocols/UserPropertyFactoryTests.swift @@ -0,0 +1,35 @@ +// +// UserPropertyFactoryTests.swift +// YAnalytics +// +// Created by Mark Pospesel on 3/8/23. +// Copyright © 2023 Y Media Labs. All rights reserved. +// + +import XCTest +@testable import YAnalytics + +final class UserPropertyFactoryTests: XCTestCase { + func test_event_deliversEvent() { + MockUserProperty.allCases.forEach { + switch $0.event { + case .userProperty(let name, let value): + XCTAssertEqual(name, $0.name) + XCTAssertEqual(value, $0.value) + XCTAssertEqual(value, $0.name.capitalized) + default: + XCTFail("Unexpected event type: \($0.event)") + } + } + } +} + +private extension UserPropertyFactoryTests { + enum MockUserProperty: String, UserPropertyFactory, CaseIterable { + case abc + case def + + var name: String { rawValue } + var value: String { rawValue.capitalized } + } +}