diff --git a/Sources/FormbricksSDK/Manager/PresentSurveyManager.swift b/Sources/FormbricksSDK/Manager/PresentSurveyManager.swift index d63a085..5875cf8 100644 --- a/Sources/FormbricksSDK/Manager/PresentSurveyManager.swift +++ b/Sources/FormbricksSDK/Manager/PresentSurveyManager.swift @@ -13,14 +13,16 @@ final class PresentSurveyManager { private weak var viewController: UIViewController? /// Present the webview - func present(environmentResponse: EnvironmentResponse, id: String, overlay: SurveyOverlay = .none, completion: ((Bool) -> Void)? = nil) { + /// The native background is always `.clear` — overlay rendering is handled + /// entirely by the JS survey library inside the WebView to avoid double-overlay artifacts. + func present(environmentResponse: EnvironmentResponse, id: String, completion: ((Bool) -> Void)? = nil) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } if let window = UIApplication.safeKeyWindow { let view = FormbricksView(viewModel: FormbricksViewModel(environmentResponse: environmentResponse, surveyId: id)) let vc = UIHostingController(rootView: view) vc.modalPresentationStyle = .overCurrentContext - vc.view.backgroundColor = Self.backgroundColor(for: overlay) + vc.view.backgroundColor = .clear if let presentationController = vc.presentationController as? UISheetPresentationController { presentationController.detents = [.large()] } @@ -34,18 +36,6 @@ final class PresentSurveyManager { } } - /// Returns the appropriate background color for the given overlay style. - static func backgroundColor(for overlay: SurveyOverlay) -> UIColor { - switch overlay { - case .dark: - return UIColor(white: 0.2, alpha: 0.6) - case .light: - return UIColor(white: 0.6, alpha: 0.4) - case .none: - return .clear - } - } - /// Dismiss the webview func dismissView() { viewController?.dismiss(animated: true) diff --git a/Sources/FormbricksSDK/Manager/SurveyManager.swift b/Sources/FormbricksSDK/Manager/SurveyManager.swift index c51509e..b60a5da 100644 --- a/Sources/FormbricksSDK/Manager/SurveyManager.swift +++ b/Sources/FormbricksSDK/Manager/SurveyManager.swift @@ -120,8 +120,7 @@ final class SurveyManager { DispatchQueue.global().asyncAfter(deadline: .now() + Double(timeout)) { [weak self] in guard let self = self else { return } if let environmentResponse = self.environmentResponse { - let overlay = self.resolveOverlay(for: survey) - self.presentSurveyManager.present(environmentResponse: environmentResponse, id: survey.id, overlay: overlay) { success in + self.presentSurveyManager.present(environmentResponse: environmentResponse, id: survey.id) { success in if !success { self.isShowingSurvey = false } @@ -190,9 +189,7 @@ private extension SurveyManager { /// The view controller is presented over the current context. func showSurvey(withId id: String) { if let environmentResponse = environmentResponse { - let survey = environmentResponse.data.data.surveys?.first(where: { $0.id == id }) - let overlay = resolveOverlay(for: survey) - presentSurveyManager.present(environmentResponse: environmentResponse, id: id, overlay: overlay) + presentSurveyManager.present(environmentResponse: environmentResponse, id: id) } } @@ -347,15 +344,6 @@ extension SurveyManager { return entry.language.code } - /// Resolves the overlay style for the given survey, falling back to the project-level default. - /// Survey-level `projectOverwrites.overlay` takes precedence over `project.overlay`. - func resolveOverlay(for survey: Survey?) -> SurveyOverlay { - if let surveyOverlay = survey?.projectOverwrites?.overlay { - return surveyOverlay - } - return environmentResponse?.data.data.project.overlay ?? .none - } - /// Filters the surveys based on the user's segments. func filterSurveysBasedOnSegments(_ surveys: [Survey], segments: [String]) -> [Survey] { return surveys.filter { survey in diff --git a/Tests/FormbricksSDKTests/FormbricksSDKTests.swift b/Tests/FormbricksSDKTests/FormbricksSDKTests.swift index 2e055a3..e38e379 100644 --- a/Tests/FormbricksSDKTests/FormbricksSDKTests.swift +++ b/Tests/FormbricksSDKTests/FormbricksSDKTests.swift @@ -261,34 +261,11 @@ final class FormbricksSDKTests: XCTestCase { XCTAssertNil(manager.getLanguageCode(survey: survey, language: "spanish")) } - // MARK: - PresentSurveyManager overlay background color tests + // MARK: - PresentSurveyManager tests - func testBackgroundColorForDarkOverlay() { - let color = PresentSurveyManager.backgroundColor(for: .dark) - var white: CGFloat = 0 - var alpha: CGFloat = 0 - color.getWhite(&white, alpha: &alpha) - XCTAssertEqual(white, 0.2, accuracy: 0.01, "Dark overlay should use 0.2 white") - XCTAssertEqual(alpha, 0.6, accuracy: 0.01, "Dark overlay should use 0.6 alpha") - } - - func testBackgroundColorForLightOverlay() { - let color = PresentSurveyManager.backgroundColor(for: .light) - var white: CGFloat = 0 - var alpha: CGFloat = 0 - color.getWhite(&white, alpha: &alpha) - XCTAssertEqual(white, 0.6, accuracy: 0.01, "Light overlay should use 0.6 white") - XCTAssertEqual(alpha, 0.4, accuracy: 0.01, "Light overlay should use 0.4 alpha") - } - - func testBackgroundColorForNoneOverlay() { - let color = PresentSurveyManager.backgroundColor(for: .none) - XCTAssertEqual(color, .clear, "None overlay should return clear color") - } - - func testPresentWithOverlayCompletesInHeadlessEnvironment() { + func testPresentCompletesInHeadlessEnvironment() { // In a headless test environment there is no key window, so present() should - // call the completion with false for every overlay variant. + // call the completion with false. let config = FormbricksConfig.Builder(appUrl: appUrl, environmentId: environmentId) .setLogLevel(.debug) .service(mockService) @@ -306,99 +283,13 @@ final class FormbricksSDKTests: XCTestCase { } let manager = PresentSurveyManager() - - for overlay in [SurveyOverlay.none, .light, .dark] { - let presentExpectation = expectation(description: "Present with \(overlay.rawValue) overlay") - manager.present(environmentResponse: env, id: surveyID, overlay: overlay) { success in - // No key window in headless tests → completion(false) - XCTAssertFalse(success, "Presentation should fail in headless environment for overlay: \(overlay.rawValue)") - presentExpectation.fulfill() - } - wait(for: [presentExpectation], timeout: 2.0) - } - } - - // MARK: - SurveyManager.resolveOverlay tests - - func testResolveOverlayUsesSurveyOverwrite() { - let config = FormbricksConfig.Builder(appUrl: appUrl, environmentId: environmentId) - .setLogLevel(.debug) - .service(mockService) - .build() - Formbricks.setup(with: config) - - Formbricks.surveyManager?.refreshEnvironmentIfNeeded(force: true) - let loadExpectation = expectation(description: "Env loaded") - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { loadExpectation.fulfill() } - wait(for: [loadExpectation]) - - guard let manager = Formbricks.surveyManager else { - XCTFail("Missing surveyManager") - return - } - - // The mock survey has projectOverwrites.overlay = "dark" - let surveyWithOverwrite = manager.environmentResponse?.data.data.surveys?.first(where: { $0.id == surveyID }) - XCTAssertNotNil(surveyWithOverwrite) - XCTAssertEqual(manager.resolveOverlay(for: surveyWithOverwrite), .dark, - "Should use survey-level projectOverwrites overlay") - } - - func testResolveOverlayFallsBackToProjectDefault() { - let config = FormbricksConfig.Builder(appUrl: appUrl, environmentId: environmentId) - .setLogLevel(.debug) - .service(mockService) - .build() - Formbricks.setup(with: config) - - Formbricks.surveyManager?.refreshEnvironmentIfNeeded(force: true) - let loadExpectation = expectation(description: "Env loaded") - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { loadExpectation.fulfill() } - wait(for: [loadExpectation]) - - guard let manager = Formbricks.surveyManager else { - XCTFail("Missing surveyManager") - return + let presentExpectation = expectation(description: "Present completes") + manager.present(environmentResponse: env, id: surveyID) { success in + // No key window in headless tests → completion(false) + XCTAssertFalse(success, "Presentation should fail in headless environment") + presentExpectation.fulfill() } - - // A survey without projectOverwrites should fall back to project.overlay ("none" in mock) - let surveyWithoutOverwrite = Survey( - id: "no-overwrite", - name: "No Overwrite", - triggers: nil, - recontactDays: nil, - displayLimit: nil, - delay: nil, - displayPercentage: nil, - displayOption: .respondMultiple, - segment: nil, - styling: nil, - languages: nil, - projectOverwrites: nil - ) - XCTAssertEqual(manager.resolveOverlay(for: surveyWithoutOverwrite), .none, - "Should fall back to project-level overlay when no survey overwrite exists") - } - - func testResolveOverlayReturnsNoneForNilSurvey() { - let config = FormbricksConfig.Builder(appUrl: appUrl, environmentId: environmentId) - .setLogLevel(.debug) - .service(mockService) - .build() - Formbricks.setup(with: config) - - Formbricks.surveyManager?.refreshEnvironmentIfNeeded(force: true) - let loadExpectation = expectation(description: "Env loaded") - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { loadExpectation.fulfill() } - wait(for: [loadExpectation]) - - guard let manager = Formbricks.surveyManager else { - XCTFail("Missing surveyManager") - return - } - - XCTAssertEqual(manager.resolveOverlay(for: nil), .none, - "Should return .none when survey is nil") + wait(for: [presentExpectation], timeout: 2.0) } // MARK: - WebView data tests