Skip to content

Commit d00d076

Browse files
fix: adds coverage (#10)
* adds coverage * bumps ruby version * fixes test command * fixes iphone sdk error * action * action * breaks action into steps * fixes action * fixes action * fixes action * fixes action * fixes action * fixes action * fixes action * fixes action * fixes action * fixes action * update action * update action * update to 18.4 * fix first tests * fix all tests * pin sonarcloud github action * fix action build step * update simulator version and verify test bundle * fix build output location * mocks * mocks * mocks * adds logs * adds logs * adds logs * adds logs * adds logs * adds logs * adds logs * lock macos version * modify mock * adds logs * fix action * cleanup * action tests * fixes comma error * tests * tests * tests * tests * tests * tests * tests * tests * tests * tests * adjusts timeouts * adjusts timeouts * adjusts timeouts * adjusts timeouts * adjusts timeouts * adds completion callback to track method * tests * tests * adds cleanup test * edits test * edits test * edits test * edits test * edits test * edits coverage action * edits coverage action * edits coverage action * edits coverage action * edits coverage action * edits coverage action * edits coverage action * edits coverage action * edits coverage action * edits coverage action * edits coverage action --------- Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
1 parent d391561 commit d00d076

File tree

14 files changed

+351
-130
lines changed

14 files changed

+351
-130
lines changed

.github/workflows/sonarqube.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: SonarQube
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- main
8+
pull_request:
9+
types: [opened, synchronize, reopened]
10+
merge_group:
11+
12+
jobs:
13+
build-and-analyze:
14+
runs-on: macos-15
15+
16+
steps:
17+
- name: Checkout code
18+
uses: actions/checkout@v3
19+
with:
20+
fetch-depth: 0 # Shallow clones should be disabled for better SonarQube analysis
21+
22+
- name: Install Dependencies
23+
run: swift package resolve
24+
25+
- name: Run tests
26+
run: |
27+
xcodebuild test \
28+
-scheme 'FormbricksSDK' \
29+
-sdk iphonesimulator \
30+
-config Debug \
31+
-destination 'platform=iOS Simulator,name=iPhone SE (3rd generation),OS=18.2' \
32+
-derivedDataPath build \
33+
-enableCodeCoverage YES
34+
35+
- name: Extract code coverage and convert to XML
36+
run: |
37+
mkdir -p coverage
38+
bash ./scripts/xccov-to-sonarqube-generic.sh build/Logs/Test/*.xcresult > coverage/coverage.xml
39+
40+
# Run SonarCloud scan, pointing at the Cobertura report
41+
- name: SonarCloud Scan
42+
uses: SonarSource/sonarqube-scan-action@v5.2.0
43+
env:
44+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
46+
with:
47+
args: >
48+
-Dsonar.organization=formbricks
49+
-Dsonar.projectKey=formbricks_ios
50+
-Dsonar.sources=Sources/FormbricksSDK
51+
-Dsonar.tests=Tests/FormbricksSDKTests
52+
-Dsonar.coverageReportPaths=coverage/coverage.xml
53+
-Dsonar.verbose=true
54+
-Dsonar.exclusions=**/Mock/**,**/*.xcodeproj/**,**/.swiftpm/**
55+
-Dsonar.c.file.suffixes=-
56+
-Dsonar.cpp.file.suffixes=-
57+
-Dsonar.objc.file.suffixes=-

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ playground.xcworkspace
2929
# hence it is not needed unless you have added a package configuration file to your project
3030
# .swiftpm
3131

32+
build/
33+
3234
.build/
3335
.swiftpm/
3436

Package.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ let package = Package(
2020
.testTarget(
2121
name: "FormbricksSDKTests",
2222
dependencies: ["FormbricksSDK"],
23-
path: "Tests/FormbricksSDKTests"
23+
resources: [
24+
.process("Mock/User.json"),
25+
.process("Mock/Environment.json"),
26+
]
2427
)
2528
]
2629
)

Sources/FormbricksSDK/Formbricks.swift

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import Network
1414
static internal var surveyManager: SurveyManager?
1515
static internal var apiQueue: OperationQueue? = OperationQueue()
1616
static internal var logger: Logger?
17-
static internal var service = FormbricksService()
1817

1918
// make this class not instantiatable outside of the SDK
2019
internal override init() {
@@ -57,7 +56,17 @@ import Network
5756
self.environmentId = config.environmentId
5857
self.logger?.logLevel = config.logLevel
5958

59+
let svc: FormbricksServiceProtocol = config.customService
60+
?? {
61+
guard URL(string: config.appUrl) != nil else {
62+
fatalError("Invalid appUrl")
63+
}
64+
65+
return FormbricksService()
66+
}()
67+
6068
userManager = UserManager()
69+
userManager?.service = svc
6170
if let userId = config.userId {
6271
userManager?.set(userId: userId)
6372
}
@@ -70,7 +79,7 @@ import Network
7079
}
7180

7281
presentSurveyManager = PresentSurveyManager()
73-
surveyManager = SurveyManager.create(userManager: userManager!, presentSurveyManager: presentSurveyManager!)
82+
surveyManager = SurveyManager.create(userManager: userManager!, presentSurveyManager: presentSurveyManager!, service: svc)
7483
userManager?.surveyManager = surveyManager
7584

7685
surveyManager?.refreshEnvironmentIfNeeded(force: force)
@@ -143,26 +152,25 @@ import Network
143152

144153
/**
145154
Sets the language for the current user with the given `String`.
146-
The SDK must be initialized before calling this method.
155+
This method can be called before or after SDK initialization.
147156

148157
Example:
149158
```swift
150159
Formbricks.setLanguage("de")
151160
```
152161
*/
153162
@objc public static func setLanguage(_ language: String) {
154-
guard Formbricks.isInitialized else {
155-
let error = FormbricksSDKError(type: .sdkIsNotInitialized)
156-
Formbricks.logger?.error(error.message)
157-
return
158-
}
159-
163+
// Set the language property regardless of initialization status
160164
if (Formbricks.language == language) {
161165
return
162166
}
163167

164168
Formbricks.language = language
165-
userManager?.set(language: language)
169+
170+
// Only update the user manager if SDK is initialized
171+
if Formbricks.isInitialized {
172+
userManager?.set(language: language)
173+
}
166174
}
167175

168176
/**
@@ -174,7 +182,7 @@ import Network
174182
Formbricks.track("button_clicked")
175183
```
176184
*/
177-
@objc public static func track(_ action: String) {
185+
@objc public static func track(_ action: String, completion: (() -> Void)? = nil) {
178186
guard Formbricks.isInitialized else {
179187
let error = FormbricksSDKError(type: .sdkIsNotInitialized)
180188
Formbricks.logger?.error(error.message)
@@ -183,7 +191,7 @@ import Network
183191

184192
Formbricks.isInternetAvailabile { available in
185193
if available {
186-
surveyManager?.track(action)
194+
surveyManager?.track(action, completion: completion)
187195
} else {
188196
Formbricks.logger?.warning(FormbricksSDKError.init(type: .networkError).message)
189197
}

Sources/FormbricksSDK/Helpers/ConfigBuilder.swift

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ import Foundation
77
let userId: String?
88
let attributes: [String:String]?
99
let logLevel: LogLevel
10+
/// Optional custom service, injected via Builder
11+
let customService: FormbricksServiceProtocol?
1012

11-
init(appUrl: String, environmentId: String, userId: String?, attributes: [String : String]?, logLevel: LogLevel) {
12-
self.appUrl = appUrl
13-
self.environmentId = environmentId
14-
self.userId = userId
15-
self.attributes = attributes
16-
self.logLevel = logLevel
13+
init(appUrl: String, environmentId: String, userId: String?, attributes: [String : String]?, logLevel: LogLevel, customService: FormbricksServiceProtocol?) {
14+
self.appUrl = appUrl
15+
self.environmentId = environmentId
16+
self.userId = userId
17+
self.attributes = attributes
18+
self.logLevel = logLevel
19+
self.customService = customService
1720
}
1821

1922
/// The builder class for the FormbricksConfig object.
@@ -23,6 +26,8 @@ import Foundation
2326
var userId: String?
2427
var attributes: [String:String] = [:]
2528
var logLevel: LogLevel = .error
29+
/// Optional custom service, injected via Builder
30+
var customService: FormbricksServiceProtocol?
2631

2732
@objc public init(appUrl: String, environmentId: String) {
2833
self.appUrl = appUrl
@@ -53,9 +58,14 @@ import Foundation
5358
return self
5459
}
5560

61+
func service(_ svc: FormbricksServiceProtocol) -> FormbricksConfig.Builder {
62+
self.customService = svc
63+
return self
64+
}
65+
5666
/// Builds the FormbricksConfig object from the Builder object.
5767
@objc public func build() -> FormbricksConfig {
58-
return FormbricksConfig(appUrl: appUrl, environmentId: environmentId, userId: userId, attributes: attributes, logLevel: logLevel)
68+
return FormbricksConfig(appUrl: appUrl, environmentId: environmentId, userId: userId, attributes: attributes, logLevel: logLevel, customService: customService)
5969
}
6070
}
6171
}

Sources/FormbricksSDK/Manager/SurveyManager.swift

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,30 @@ import SwiftUI
55
final class SurveyManager {
66
private let userManager: UserManager
77
private let presentSurveyManager: PresentSurveyManager
8-
9-
private init(userManager: UserManager, presentSurveyManager: PresentSurveyManager) {
8+
internal var service: FormbricksServiceProtocol
9+
10+
// Private initializer supports dependency injection
11+
private init(userManager: UserManager, presentSurveyManager: PresentSurveyManager, service: FormbricksServiceProtocol = FormbricksService()) {
1012
self.userManager = userManager
1113
self.presentSurveyManager = presentSurveyManager
14+
self.service = service
1215
}
1316

14-
static func create(userManager: UserManager, presentSurveyManager: PresentSurveyManager) -> SurveyManager {
15-
return SurveyManager(userManager: userManager, presentSurveyManager: presentSurveyManager)
16-
}
17+
static func create(
18+
userManager: UserManager,
19+
presentSurveyManager: PresentSurveyManager,
20+
service: FormbricksServiceProtocol = FormbricksService()
21+
) -> SurveyManager {
22+
return SurveyManager(
23+
userManager: userManager,
24+
presentSurveyManager: presentSurveyManager,
25+
service: service
26+
)
27+
}
28+
29+
// internal var service = FormbricksService()
1730

1831
private static let environmentResponseObjectKey = "environmentResponseObjectKey"
19-
internal var service = FormbricksService()
2032
private var backingEnvironmentResponse: EnvironmentResponse?
2133
/// Stores the surveys that are filtered based on the defined criteria, such as recontact days, display options etc.
2234
internal private(set) var filteredSurveys: [Survey] = []
@@ -50,7 +62,7 @@ final class SurveyManager {
5062

5163
/// Checks if there are any surveys to display, based in the track action, and if so, displays the first one.
5264
/// Handles the display percentage and the delay of the survey.
53-
func track(_ action: String) {
65+
func track(_ action: String, completion: (() -> Void)? = nil) {
5466
guard !isShowingSurvey else { return }
5567

5668
let actionClasses = environmentResponse?.data.data.actionClasses ?? []
@@ -81,6 +93,7 @@ final class SurveyManager {
8193
let timeout = firstSurveyWithActionClass?.delay ?? 0
8294
DispatchQueue.global().asyncAfter(deadline: .now() + Double(timeout)) { [weak self] in
8395
self?.showSurvey(withId: surveyId)
96+
completion?()
8497
}
8598
}
8699
}

Sources/FormbricksSDK/Manager/UserManager.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import Foundation
33
/// Store and manage user state and sync with the server when needed.
44
final class UserManager: UserManagerSyncable {
55
weak var surveyManager: SurveyManager?
6+
internal var service: FormbricksServiceProtocol
67

7-
init(surveyManager: SurveyManager? = nil) {
8+
init(surveyManager: SurveyManager? = nil, service: FormbricksServiceProtocol = FormbricksService()) {
89
self.surveyManager = surveyManager
10+
self.service = service
911
}
1012

1113
private static let userIdKey = "userIdKey"
@@ -16,7 +18,7 @@ final class UserManager: UserManagerSyncable {
1618
private static let lastDisplayedAtKey = "lastDisplayedAtKey"
1719
private static let expiresAtKey = "expiresAtKey"
1820

19-
internal var service = FormbricksService()
21+
// internal var service = FormbricksService()
2022

2123
private var backingUserId: String?
2224
private var backingContactId: String?

Sources/FormbricksSDK/Networking/Service/FormbricksService.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/// FormbricksService is a service class that handles the network requests for Formbricks API.
2-
class FormbricksService {
2+
class FormbricksService: FormbricksServiceProtocol {
33

44
// MARK: - Environment -
55
/// Get the current environment state.
@@ -16,6 +16,17 @@ class FormbricksService {
1616
}
1717
}
1818

19+
protocol FormbricksServiceProtocol {
20+
func getEnvironmentState(
21+
completion: @escaping (ResultType<GetEnvironmentRequest.Response>) -> Void
22+
)
23+
func postUser(
24+
id: String,
25+
attributes: [String: String]?,
26+
completion: @escaping (ResultType<PostUserRequest.Response>) -> Void
27+
)
28+
}
29+
1930
private extension FormbricksService {
2031
/// Creates the APIClient operation and adds it to the queue
2132
func execute<Request: CodableRequest>(_ request: Request, withCompletion completion: @escaping (ResultType<Request.Response>) -> Void) {

0 commit comments

Comments
 (0)