Skip to content

Commit 919df0a

Browse files
Mark Pospeselmpospese
Mark Pospesel
authored andcommitted
[Issue-7] Add Event Factory
1 parent 5760d37 commit 919df0a

File tree

9 files changed

+253
-0
lines changed

9 files changed

+253
-0
lines changed

Sources/YAnalytics/Protocol/AnalyticsEngine.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,19 @@ public protocol AnalyticsEngine {
1313
/// Track an analytics event
1414
/// - Parameter event: the event to log
1515
func track(event: AnalyticsEvent)
16+
17+
/// Tracks an analytics event
18+
/// - Parameter factory: object that generates the event to log
19+
func track(event factory: AnalyticsEventFactory)
20+
}
21+
22+
/// Default implementation of `track(factory:)`
23+
extension AnalyticsEngine {
24+
/// Tracks an analytics event.
25+
///
26+
/// Extracts the event from the factory and tracks that.
27+
/// - Parameter factory: object that generates the event to log
28+
public func track(event factory: AnalyticsEventFactory) {
29+
track(event: factory.event)
30+
}
1631
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// AnalyticsEventFactory.swift
3+
// YAnalytics
4+
//
5+
// Created by Mark Pospesel on 3/8/23.
6+
// Copyright © 2023 Y Media Labs. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
/// Anything that generates an analytics event.
12+
///
13+
/// Three protocols, `ScreenViewFactory`, `UserPropertyFactory`, and `EventFactory`, conform to `AnalyticsEventFactory`;
14+
/// one for each of the three cases in the `AnalyticsEvent` enum.
15+
public protocol AnalyticsEventFactory {
16+
/// The event
17+
var event: AnalyticsEvent { get }
18+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// EventFactory.swift
3+
// YAnalytics
4+
//
5+
// Created by Mark Pospesel on 3/8/23.
6+
// Copyright © 2023 Y Media Labs. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
/// Anything that generates an analytics event of type `AnalyticsEvent.event`
12+
public protocol EventFactory: AnalyticsEventFactory {
13+
/// Event name
14+
var name: String { get }
15+
/// Event parameters
16+
var parameters: Metadata? { get }
17+
}
18+
19+
/// Default implementation of `event` property
20+
public extension EventFactory {
21+
/// Generates an event (of type `.event`) from `name` and `parameters`
22+
var event: AnalyticsEvent {
23+
.event(name: name, parameters: parameters)
24+
}
25+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// ScreenViewFactory.swift
3+
// YAnalytics
4+
//
5+
// Created by Mark Pospesel on 3/8/23.
6+
// Copyright © 2023 Y Media Labs. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
/// Anything that generates an analytics event of type `AnalyticsEvent.screenView`
12+
public protocol ScreenViewFactory: AnalyticsEventFactory {
13+
/// Screen name
14+
var screenName: String { get }
15+
}
16+
17+
/// Default implementation of `event` property
18+
public extension ScreenViewFactory {
19+
/// Generates an event (of type `.screenView`) from `screenName`
20+
var event: AnalyticsEvent {
21+
.screenView(screenName: screenName)
22+
}
23+
}
24+
25+
/// RawRepresentable (e.g. all string-based enums) conforms to ScreenViewFactory by simply returning its raw value
26+
extension RawRepresentable where RawValue == String {
27+
/// URL path string
28+
public var screenName: String { rawValue }
29+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// UserPropertyFactory.swift
3+
// YAnalytics
4+
//
5+
// Created by Mark Pospesel on 3/8/23.
6+
// Copyright © 2023 Y Media Labs. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
/// Anything that generates an analytics event of type `AnalyticsEvent.userProperty`
12+
public protocol UserPropertyFactory: AnalyticsEventFactory {
13+
/// Property name
14+
var name: String { get }
15+
/// Property value
16+
var value: String { get }
17+
}
18+
19+
/// Default implementation of `event` property
20+
public extension UserPropertyFactory {
21+
/// Generates an event (of type `.userProperty`) from `name` and `value`
22+
var event: AnalyticsEvent {
23+
.userProperty(name: name, value: value)
24+
}
25+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//
2+
// AnalyticsEngineTests.swift
3+
// YAnalytics
4+
//
5+
// Created by Mark Pospesel on 3/8/23.
6+
// Copyright © 2023 Y Media Labs. All rights reserved.
7+
//
8+
9+
import XCTest
10+
@testable import YAnalytics
11+
12+
final class AnalyticsEngineTests: XCTestCase {
13+
func test_trackFactory_tracksEvents() {
14+
// Given
15+
let engine = MockAnalyticsEngine()
16+
17+
// When
18+
MockEvent.allCases.forEach {
19+
engine.track(event: $0)
20+
}
21+
22+
// Then
23+
XCTAssertEqual(engine.allEvents.count, 3)
24+
XCTAssertEqual(engine.screenViews.joined(), "abcdefxyz")
25+
}
26+
}
27+
28+
private extension AnalyticsEngineTests {
29+
enum MockEvent: String, CaseIterable, ScreenViewFactory {
30+
case abc
31+
case def
32+
case xyz
33+
}
34+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//
2+
// EventFactoryTests.swift
3+
// YAnalytics
4+
//
5+
// Created by Mark Pospesel on 3/8/23.
6+
// Copyright © 2023 Y Media Labs. All rights reserved.
7+
//
8+
9+
import XCTest
10+
@testable import YAnalytics
11+
12+
final class EventFactoryTests: XCTestCase {
13+
func test_event_deliversEvent() throws {
14+
try MockEvent.allCases.forEach {
15+
switch $0.event {
16+
case .event(let name, let parameters):
17+
XCTAssertEqual(name, $0.name)
18+
let parameters = try XCTUnwrap(parameters as? [String: String])
19+
let expected = try XCTUnwrap($0.parameters as? [String: String])
20+
XCTAssertEqual(parameters, expected)
21+
default:
22+
XCTFail("Unexpected event type: \($0.event)")
23+
}
24+
}
25+
}
26+
}
27+
28+
private extension EventFactoryTests {
29+
enum MockEvent: String, EventFactory, CaseIterable {
30+
case abc
31+
case def
32+
33+
var name: String { rawValue }
34+
var parameters: Metadata? {
35+
[
36+
"value": "42\(rawValue.uppercased())",
37+
"type": "dictionary"
38+
]
39+
}
40+
}
41+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//
2+
// ScreenViewFactoryTests.swift
3+
// YAnalytics
4+
//
5+
// Created by Mark Pospesel on 3/8/23.
6+
// Copyright © 2023 Y Media Labs. All rights reserved.
7+
//
8+
9+
import XCTest
10+
@testable import YAnalytics
11+
12+
final class ScreenViewFactoryTests: XCTestCase {
13+
func test_event_deliversEvent() {
14+
MockScreenView.allCases.forEach {
15+
switch $0.event {
16+
case .screenView(let screenName):
17+
XCTAssertEqual(screenName, $0.screenName)
18+
XCTAssertEqual(screenName, $0.rawValue)
19+
default:
20+
XCTFail("Unexpected event type: \($0.event)")
21+
}
22+
}
23+
}
24+
}
25+
26+
private extension ScreenViewFactoryTests {
27+
enum MockScreenView: String, ScreenViewFactory, CaseIterable {
28+
case abc
29+
case def
30+
}
31+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//
2+
// UserPropertyFactoryTests.swift
3+
// YAnalytics
4+
//
5+
// Created by Mark Pospesel on 3/8/23.
6+
// Copyright © 2023 Y Media Labs. All rights reserved.
7+
//
8+
9+
import XCTest
10+
@testable import YAnalytics
11+
12+
final class UserPropertyFactoryTests: XCTestCase {
13+
func test_event_deliversEvent() {
14+
MockUserProperty.allCases.forEach {
15+
switch $0.event {
16+
case .userProperty(let name, let value):
17+
XCTAssertEqual(name, $0.name)
18+
XCTAssertEqual(value, $0.value)
19+
XCTAssertEqual(value, $0.name.capitalized)
20+
default:
21+
XCTFail("Unexpected event type: \($0.event)")
22+
}
23+
}
24+
}
25+
}
26+
27+
private extension UserPropertyFactoryTests {
28+
enum MockUserProperty: String, UserPropertyFactory, CaseIterable {
29+
case abc
30+
case def
31+
32+
var name: String { rawValue }
33+
var value: String { rawValue.capitalized }
34+
}
35+
}

0 commit comments

Comments
 (0)