diff --git a/CountlyTests/CountlyBaseTestCase.swift b/CountlyTests/CountlyBaseTestCase.swift index 90a5f60e..d786fa86 100644 --- a/CountlyTests/CountlyBaseTestCase.swift +++ b/CountlyTests/CountlyBaseTestCase.swift @@ -13,7 +13,7 @@ class CountlyBaseTestCase: XCTestCase { var countly: Countly! var deviceID: String = "" let appKey: String = "appkey" - var host: String = "https://test.count.ly/" + var host: String = "https://testing.count.ly/" override func setUpWithError() throws { // Put setup code here. This method is called before the invocation of each test method in the class. diff --git a/CountlyTests/CountlyViewTests.swift b/CountlyTests/CountlyViewTests.swift index 54f7c1de..38dee23b 100644 --- a/CountlyTests/CountlyViewTests.swift +++ b/CountlyTests/CountlyViewTests.swift @@ -10,190 +10,506 @@ import XCTest class CountlyViewTrackingTests: CountlyBaseTestCase { - func checkPersistentValues() { - let countlyPersistency = CountlyPersistency.sharedInstance() - if(countlyPersistency != nil) { - if let queuedRequests = CountlyPersistency.sharedInstance().value(forKey: "queuedRequests") as? NSMutableArray, - let recordedEvents = CountlyPersistency.sharedInstance().value(forKey: "recordedEvents") as? NSMutableArray, - let startedEvents = CountlyPersistency.sharedInstance().value(forKey: "startedEvents") as? NSMutableDictionary, - let isQueueBeingModified = CountlyPersistency.sharedInstance().value(forKey: "isQueueBeingModified") as? Bool { - print("Successfully access private properties.") - - - } - else { - print("Failed to access private properties.") - } + func testStartAndStopView() throws { + let config = createBaseConfig() + Countly.sharedInstance().start(with: config) + + let viewID = Countly.sharedInstance().views().startView("View1") + XCTAssertNotNil(viewID, "View should be started successfully.") + + let expectation = XCTestExpectation(description: "Wait for 3 seconds before stopping the view.") + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + Countly.sharedInstance().views().stopView(withName: "View1") + expectation.fulfill() } + wait(for: [expectation], timeout: 5.0) // Wait for the expectation to be fulfilled + checkRecordedEventsForView(viewName: "View1") } - func testViewForegroundBackground() { + func testStartAndStopViewWithSegmentation() throws { let config = createBaseConfig() - // No Device ID provided during init Countly.sharedInstance().start(with: config) - let viewID = Countly.sharedInstance().views().startAutoStoppedView("TestAutoStoppedView") + let viewID = Countly.sharedInstance().views().startView("View1", segmentation: ["key": "value"]) + XCTAssertNotNil(viewID, "View should be started successfully with segmentation.") - let pauseViewExpectation = XCTestExpectation(description: "Wait for pause view") - DispatchQueue.global().asyncAfter(deadline: .now() + 5) { - Countly.sharedInstance().views().pauseView(withID: viewID) - pauseViewExpectation.fulfill() + let expectation = XCTestExpectation(description: "Wait for 4 seconds before stopping the view.") + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + Countly.sharedInstance().views().stopView(withName: "View1") + expectation.fulfill() } - let resumeViewExpectation = XCTestExpectation(description: "Wait for resume view") - DispatchQueue.global().asyncAfter(deadline: .now() + 10) { // Delayed by 10 seconds - Countly.sharedInstance().views().resumeView(withID: viewID) - resumeViewExpectation.fulfill() - } + wait(for: [expectation], timeout: 5.0) // Wait for the expectation to be fulfilled + checkRecordedEventsForView(viewName: "View1", segmentation: ["key": "value"]) + } + + func testStartViewAndStopViewWithID() throws { + let config = createBaseConfig() + Countly.sharedInstance().start(with: config) - let bgExpectation = XCTestExpectation(description: "Wait for background notification") - DispatchQueue.global().asyncAfter(deadline: .now() + 15) { // Delayed by 15 seconds - NotificationCenter.default.post(name: UIApplication.didEnterBackgroundNotification, object: nil) - bgExpectation.fulfill() + let viewID = Countly.sharedInstance().views().startView("View1") ?? "" + XCTAssertNotNil(viewID, "View should be started successfully.") + + let expectation = XCTestExpectation(description: "Wait for 3 seconds before stopping the view.") + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + Countly.sharedInstance().views().stopView(withID: viewID) + expectation.fulfill() } - let fgExpectation = XCTestExpectation(description: "Wait for active notification") - DispatchQueue.global().asyncAfter(deadline: .now() + 20) { // Delayed by 20 seconds - NotificationCenter.default.post(name: UIApplication.didBecomeActiveNotification, object: nil) - fgExpectation.fulfill() + wait(for: [expectation], timeout: 5.0) // Wait for the expectation to be fulfilled + checkRecordedEventsForView(withID: viewID) + } + + func testStartAndStopMultipleViewsIncludingAutoStoppedViews() throws { + let config = createBaseConfig() + Countly.sharedInstance().start(with: config) + + let viewID1 = Countly.sharedInstance().views().startView("View1") + let viewID2 = Countly.sharedInstance().views().startAutoStoppedView("View2") + XCTAssertNotNil(viewID1, "View1 should be started successfully.") + XCTAssertNotNil(viewID2, "View2 should be started successfully.") + + let expectation = XCTestExpectation(description: "Wait for 5 seconds before stopping the views.") + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { + Countly.sharedInstance().views().stopView(withName: "View1") + Countly.sharedInstance().views().stopView(withID: viewID2) + expectation.fulfill() } - let bgExpectation1 = XCTestExpectation(description: "Wait for second background notification") - DispatchQueue.global().asyncAfter(deadline: .now() + 25) { // Delayed by 25 seconds - NotificationCenter.default.post(name: UIApplication.didEnterBackgroundNotification, object: nil) - bgExpectation1.fulfill() + wait(for: [expectation], timeout: 6.0) // Wait for the expectation to be fulfilled + checkRecordedEventsForView(viewName: "View1") + checkRecordedEventsForView(withID: viewID2) + } + + func testPauseAndResumeViewsForMultipleViews() throws { + let config = createBaseConfig() + Countly.sharedInstance().start(with: config) + + let viewID1 = Countly.sharedInstance().views().startView("View1") + let viewID2 = Countly.sharedInstance().views().startAutoStoppedView("View2") + XCTAssertNotNil(viewID1, "View1 should be started successfully.") + XCTAssertNotNil(viewID2, "View2 should be started successfully.") + + let expectation = XCTestExpectation(description: "Wait for 4 seconds before pausing the view.") + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + Countly.sharedInstance().views().pauseView(withID: viewID1) + + // Now wait for 3 seconds before resuming the view + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + Countly.sharedInstance().views().resumeView(withID: viewID1) + + // Wait for another 4 seconds before stopping the views + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + Countly.sharedInstance().views().stopView(withName: "View1") + Countly.sharedInstance().views().stopView(withID: viewID2) + expectation.fulfill() + } + } } - let fgExpectation1 = XCTestExpectation(description: "Wait for second active notification") - DispatchQueue.global().asyncAfter(deadline: .now() + 30) { // Delayed by 30 seconds - NotificationCenter.default.post(name: UIApplication.didBecomeActiveNotification, object: nil) - fgExpectation1.fulfill() + wait(for: [expectation], timeout: 15.0) // Wait for the expectation to be fulfilled + checkRecordedEventsForView(viewName: "View1") + checkRecordedEventsForView(withID: viewID2) + } + + func testMultiplePauseAndResumeCyclesOnSameView() throws { + let config = createBaseConfig() + Countly.sharedInstance().start(with: config) + + let viewID = Countly.sharedInstance().views().startView("View1") + XCTAssertNotNil(viewID, "View should be started successfully.") + + let expectation = XCTestExpectation(description: "Wait for 4 seconds before pausing the view.") + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + Countly.sharedInstance().views().pauseView(withID: viewID) + + let resumeExpectation = XCTestExpectation(description: "Wait for 3 seconds before resuming the view.") + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + Countly.sharedInstance().views().resumeView(withID: viewID) + + let stopExpectation = XCTestExpectation(description: "Wait for 4 seconds before stopping the view.") + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + Countly.sharedInstance().views().stopView(withName: "View1") + stopExpectation.fulfill() + } + self.wait(for: [stopExpectation], timeout: 5.0) + } + self.wait(for: [resumeExpectation], timeout: 13.0) } - // Wait for all expectations or timeout - wait(for: [pauseViewExpectation, resumeViewExpectation, bgExpectation, fgExpectation, bgExpectation1, fgExpectation1], timeout: 35) + wait(for: [expectation], timeout: 10.0) + checkRecordedEventsForView(viewName: "View1") + } + + func testStartViewWhileAutoViewTrackingEnabled() throws { + let config = createBaseConfig() + config.enableAutomaticViewTracking = true + Countly.sharedInstance().start(with: config) + + let viewID = Countly.sharedInstance().views().startView("View1") + XCTAssertNil(viewID, "Manual view tracking should be ignored when auto view tracking is enabled.") + } + + func testStartAndStopAutoStoppedViewWithSegmentation() throws { + let config = createBaseConfig() + Countly.sharedInstance().start(with: config) - let viewID1 = Countly.sharedInstance().views().startView("startView") + let viewID = Countly.sharedInstance().views().startAutoStoppedView("View1", segmentation: ["key": "value"]) + XCTAssertNotNil(viewID, "Auto-stopped view should be started successfully with segmentation.") - checkPersistentValues() - Countly.sharedInstance().views().stopAllViews(nil); + let expectation = XCTestExpectation(description: "Wait for 4 seconds before stopping the view.") + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + Countly.sharedInstance().views().stopView(withID: viewID) + expectation.fulfill() + } - checkPersistentValues() + wait(for: [expectation], timeout: 5.0) // Wait for the expectation to be fulfilled + checkRecordedEventsForView(withID: viewID, segmentation: ["key": "value"]) } - - func testViewTrackingInit_CNR_AV() throws { + func testStartAutoStoppedViewAndInitiateAnother() throws { let config = createBaseConfig() Countly.sharedInstance().start(with: config) - let viewID = Countly.sharedInstance().views().startAutoStoppedView("TestAutoStoppedView") + let viewID1 = Countly.sharedInstance().views().startAutoStoppedView("View1") + XCTAssertNotNil(viewID1, "View1 should be started successfully.") + var viewID2 = "" + let expectation = XCTestExpectation(description: "Wait for 4 seconds before starting the second view.") + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + viewID2 = Countly.sharedInstance().views().startAutoStoppedView("View2") + XCTAssertNotNil(viewID2, "View2 should be started successfully.") + + let stopExpectation = XCTestExpectation(description: "Wait for 3 seconds before stopping both views.") + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + Countly.sharedInstance().views().stopView(withID: viewID1) + Countly.sharedInstance().views().stopView(withID: viewID2) + + stopExpectation.fulfill() + } + self.wait(for: [stopExpectation], timeout: 5.0) + } + + wait(for: [expectation], timeout: 6.0) + checkRecordedEventsForView(withID: viewID1) + checkRecordedEventsForView(withID: viewID2) + } + + func testStartRegularViewPauseAndResumeMultipleTimesThenStop() throws { + let config = createBaseConfig() + Countly.sharedInstance().start(with: config) - XCTAssertNotNil(viewID, "Auto-stopped view should be started successfully.") + let viewID = Countly.sharedInstance().views().startView("View1") + XCTAssertNotNil(viewID, "View should be started successfully.") - let pauseViewExpectation = XCTestExpectation(description: "Wait for pause view") - DispatchQueue.global().asyncAfter(deadline: .now() + 5) { + let expectation = XCTestExpectation(description: "Wait for 3 seconds before pausing the view.") + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { Countly.sharedInstance().views().pauseView(withID: viewID) - pauseViewExpectation.fulfill() + + let resumeExpectation = XCTestExpectation(description: "Wait for 3 seconds before resuming the view.") + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + Countly.sharedInstance().views().resumeView(withID: viewID) + + let stopExpectation = XCTestExpectation(description: "Wait for 4 seconds before stopping the view.") + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + Countly.sharedInstance().views().stopView(withName: "View1") + stopExpectation.fulfill() + } + self.wait(for: [stopExpectation], timeout: 5.0) + } + self.wait(for: [resumeExpectation], timeout: 7.0) } - wait(for: [pauseViewExpectation], timeout: 10) + wait(for: [expectation], timeout: 10.0) + checkRecordedEventsForView(viewName: "View1") } + - func testViewTrackingInit_CR_CNG_AV() throws { + func testStopAllViewsWithSpecificSegmentation() throws { let config = createBaseConfig() - config.requiresConsent = true Countly.sharedInstance().start(with: config) - let viewID = Countly.sharedInstance().views().startAutoStoppedView("TestAutoStoppedViewWithoutConsent") - XCTAssertNil(viewID, "Auto-stopped view should not be started when consent is not given.") + Countly.sharedInstance().views().startView("View1") + Countly.sharedInstance().views().startView("View2") + + let expectation = XCTestExpectation(description: "Wait for 4 seconds before stopping all views.") + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + Countly.sharedInstance().views().stopAllViews(["key": "value"]) + expectation.fulfill() + } + + wait(for: [expectation], timeout: 5.0) // Wait for the expectation to be fulfilled + checkAllViewsStoppedWithSegmentation(["key": "value"]) } - func testViewTrackingInit_CR_CGV_AV() throws { + func testAddSegmentationToAlreadyStartedViewUsingViewName() throws { let config = createBaseConfig() - config.requiresConsent = true - config.consents = [CLYConsent.viewTracking] Countly.sharedInstance().start(with: config) - let viewID = Countly.sharedInstance().views().startAutoStoppedView("TestAutoStoppedViewWithConsent") - XCTAssertNotNil(viewID, "Auto-stopped view should be started when view tracking consent is given.") + Countly.sharedInstance().views().startView("View1") + + let expectation = XCTestExpectation(description: "Wait for 3 seconds before adding segmentation.") + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + Countly.sharedInstance().views().addSegmentationToView(withName: "View1", segmentation: ["key": "value"]) + + let stopExpectation = XCTestExpectation(description: "Wait for 4 seconds before stopping the view.") + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + Countly.sharedInstance().views().stopView(withName: "View1") + stopExpectation.fulfill() + } + self.wait(for: [stopExpectation], timeout: 5.0) + } + + wait(for: [expectation], timeout: 6.0) // Wait for the expectation to be fulfilled + checkRecordedEventsForView(viewName: "View1", segmentation: ["key": "value"]) } - func testManualViewTrackingInit_CNR_MV() throws { + func testAddSegmentationToAlreadyStartedViewUsingViewID() throws { let config = createBaseConfig() - config.manualSessionHandling = true Countly.sharedInstance().start(with: config) - let viewID = Countly.sharedInstance().views().startView("TestManualView") - XCTAssertNotNil(viewID, "Manual view should be started successfully.") + let viewID = Countly.sharedInstance().views().startView("View1") - Countly.sharedInstance().views().stopView(withID: viewID) + let expectation = XCTestExpectation(description: "Wait for 3 seconds before adding segmentation.") + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + Countly.sharedInstance().views().addSegmentationToView(withID: viewID, segmentation: ["key": "value"]) + + let stopExpectation = XCTestExpectation(description: "Wait for 4 seconds before stopping the view.") + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + Countly.sharedInstance().views().stopView(withID: viewID) + stopExpectation.fulfill() + } + self.wait(for: [stopExpectation], timeout: 5.0) + } + + wait(for: [expectation], timeout: 6.0) // Wait for the expectation to be fulfilled + checkRecordedEventsForView(withID: viewID, segmentation: ["key": "value"]) } - func testManualViewTrackingInit_CR_CNG_MV() throws { + func testStartViewWithConsentNotGiven() throws { let config = createBaseConfig() - config.manualSessionHandling = true config.requiresConsent = true Countly.sharedInstance().start(with: config) - let viewID = Countly.sharedInstance().views().startView("TestManualViewWithoutConsent") - XCTAssertNil(viewID, "Manual view should not be started when consent is not given.") + let viewID = Countly.sharedInstance().views().startView("View1") + XCTAssertNil(viewID, "Event should not be recorded when consent is not given.") + + Countly.sharedInstance().views().stopView(withName: "View1") // This should also not affect recorded events + checkNoRecordedEvents() } - func testManualViewTrackingInit_CR_CGV_MV() throws { + func testSetAndUpdateGlobalViewSegmentationWithViewInteractions() throws { let config = createBaseConfig() - config.manualSessionHandling = true - config.requiresConsent = true - config.consents = [CLYConsent.viewTracking] Countly.sharedInstance().start(with: config) - let viewID = Countly.sharedInstance().views().startView("TestManualViewWithConsent") - XCTAssertNotNil(viewID, "Manual view should be started when view tracking consent is given.") + Countly.sharedInstance().views().startView("View1") + + let expectation = XCTestExpectation(description: "Wait for 4 seconds before stopping View1.") + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + Countly.sharedInstance().views().stopView(withName: "View1") + + Countly.sharedInstance().views().setGlobalViewSegmentation(["key": "value"]) + + let startExpectation = XCTestExpectation(description: "Wait for 3 seconds before starting View2.") + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + Countly.sharedInstance().views().startView("View2") + Countly.sharedInstance().views().updateGlobalViewSegmentation(["key": "newValue"]) + + let stopExpectation = XCTestExpectation(description: "Wait for 4 seconds before stopping View2.") + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + Countly.sharedInstance().views().stopView(withName: "View2") + stopExpectation.fulfill() + } + self.wait(for: [stopExpectation], timeout: 5.0) + } + self.wait(for: [startExpectation], timeout: 7.0) + } - Countly.sharedInstance().views().stopView(withID: viewID) + wait(for: [expectation], timeout: 10.0) // Wait for the expectation to be fulfilled + checkGlobalSegmentationApplied(expected: ["key": "newValue"]) } - func testPauseAndResumeViewTracking() throws { + func testAppTransitionsToBackgroundAndForegroundWithActiveViews() throws { let config = createBaseConfig() Countly.sharedInstance().start(with: config) - let viewID = Countly.sharedInstance().views().startAutoStoppedView("TestViewPauseResume") - XCTAssertNotNil(viewID, "Auto-stopped view should be started successfully.") + Countly.sharedInstance().views().startView("View1") - let pauseViewExpectation = XCTestExpectation(description: "Wait for pause view") - DispatchQueue.global().asyncAfter(deadline: .now() + 2) { - Countly.sharedInstance().views().pauseView(withID: viewID) - pauseViewExpectation.fulfill() + let waitForStart = XCTestExpectation(description: "Wait for 3 seconds before backgrounding app.") + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + // Simulating app going to background + NotificationCenter.default.post(name: UIApplication.didEnterBackgroundNotification, object: nil) + + let waitForBackground = XCTestExpectation(description: "Wait for 4 seconds in background.") + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + // Simulating app returning to foreground + NotificationCenter.default.post(name: UIApplication.didBecomeActiveNotification, object: nil) + + let waitForForeground = XCTestExpectation(description: "Wait for 3 seconds after foregrounding.") + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + Countly.sharedInstance().views().stopView(withName: "View1") + waitForForeground.fulfill() + } + self.wait(for: [waitForForeground], timeout: 5.0) + } + self.wait(for: [waitForBackground], timeout: 5.0) } - let resumeViewExpectation = XCTestExpectation(description: "Wait for resume view") - DispatchQueue.global().asyncAfter(deadline: .now() + 4) { - Countly.sharedInstance().views().resumeView(withID: viewID) - resumeViewExpectation.fulfill() + wait(for: [waitForStart], timeout: 6.0) // Wait for the expectation to be fulfilled + checkRecordedEventsForView(viewName: "View1") + } + + func testStartMultipleViewsMoveAppToBackgroundAndReturnToForeground() throws { + let config = createBaseConfig() + Countly.sharedInstance().start(with: config) + + Countly.sharedInstance().views().startView("View1") + Countly.sharedInstance().views().startAutoStoppedView("View2") + + let waitForStart = XCTestExpectation(description: "Wait for 3 seconds before backgrounding app.") + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + // Simulating app going to background + NotificationCenter.default.post(name: UIApplication.didEnterBackgroundNotification, object: nil) + + let waitForBackground = XCTestExpectation(description: "Wait for 4 seconds in background.") + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + // Simulating app returning to foreground + NotificationCenter.default.post(name: UIApplication.didBecomeActiveNotification, object: nil) + + let waitForForeground = XCTestExpectation(description: "Wait for 3 seconds after foregrounding.") + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + Countly.sharedInstance().views().stopView(withName: "View1") + Countly.sharedInstance().views().stopView(withName: "View2") + waitForForeground.fulfill() + } + self.wait(for: [waitForForeground], timeout: 5.0) + } + self.wait(for: [waitForBackground], timeout: 5.0) } - wait(for: [pauseViewExpectation, resumeViewExpectation], timeout: 10) + wait(for: [waitForStart], timeout: 6.0) // Wait for the expectation to be fulfilled + checkRecordedEventsForView(viewName: "View1") + checkRecordedEventsForView(viewName: "View2") } - func testViewTrackingWithBackgroundAndForegroundNotifications() throws { + func testStartViewBackgroundAppResumeViewWhenReturningToForeground() throws { let config = createBaseConfig() Countly.sharedInstance().start(with: config) - let viewID = Countly.sharedInstance().views().startView("TestViewNotifications") + Countly.sharedInstance().views().startView("View1") - let bgExpectation = XCTestExpectation(description: "Wait for background notification") - DispatchQueue.global().asyncAfter(deadline: .now() + 5) { + let waitForStart = XCTestExpectation(description: "Wait for 3 seconds before backgrounding app.") + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + // Simulating app going to background NotificationCenter.default.post(name: UIApplication.didEnterBackgroundNotification, object: nil) - bgExpectation.fulfill() + + let waitForBackground = XCTestExpectation(description: "Wait for 4 seconds in background.") + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + // Simulating app returning to foreground + NotificationCenter.default.post(name: UIApplication.didBecomeActiveNotification, object: nil) + + let waitForForeground = XCTestExpectation(description: "Wait for 3 seconds after foregrounding.") + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + Countly.sharedInstance().views().stopView(withName: "View1") + waitForForeground.fulfill() + } + self.wait(for: [waitForForeground], timeout: 5.0) + } + self.wait(for: [waitForBackground], timeout: 5.0) } - let fgExpectation = XCTestExpectation(description: "Wait for foreground notification") - DispatchQueue.global().asyncAfter(deadline: .now() + 10) { - NotificationCenter.default.post(name: UIApplication.didBecomeActiveNotification, object: nil) - fgExpectation.fulfill() + wait(for: [waitForStart], timeout: 6.0) // Wait for the expectation to be fulfilled + checkRecordedEventsForView(viewName: "View1") + } + + func testAttemptToStopANonStartedView() throws { + let config = createBaseConfig() + Countly.sharedInstance().start(with: config) + + // Attempt to stop a non-started view +// let beforeEventCount = getRecordedEventCount() + Countly.sharedInstance().views().stopView(withName: "ViewNotStarted") +// let afterEventCount = getRecordedEventCount() + +// XCTAssertEqual(beforeEventCount, afterEventCount, "Stopping a non-started view should not change the state.") + } + + func testUpdateSegmentationMultipleTimesOnTheSameView() throws { + let config = createBaseConfig() + Countly.sharedInstance().start(with: config) + + let viewID = Countly.sharedInstance().views().startView("View1") + XCTAssertNotNil(viewID, "View should be started successfully.") + + let waitForStart = XCTestExpectation(description: "Wait for 4 seconds before adding segmentation.") + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + Countly.sharedInstance().views().addSegmentationToView(withName: "View1", segmentation: ["key": "value1"]) + + let waitForSecondSegmentation = XCTestExpectation(description: "Wait for 4 seconds before adding second segmentation.") + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + Countly.sharedInstance().views().addSegmentationToView(withName: "View1", segmentation: ["key": "value2"]) + + let waitForStop = XCTestExpectation(description: "Wait for 3 seconds before stopping the view.") + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + Countly.sharedInstance().views().stopView(withName: "View1") + waitForStop.fulfill() + } + self.wait(for: [waitForStop], timeout: 5.0) + } + self.wait(for: [waitForSecondSegmentation], timeout: 5.0) + } + + wait(for: [waitForStart], timeout: 6.0) // Wait for the expectation to be fulfilled + checkRecordedEventsForView(viewName: "View1", segmentation: ["key": "value2"]) // Last segmentation should apply + } + + func testBackgroundAndForegroundTriggers() throws { + let config = createBaseConfig() + Countly.sharedInstance().start(with: config) + + Countly.sharedInstance().views().startView("View1") + + let waitForStart = XCTestExpectation(description: "Wait for 3 seconds before backgrounding app.") + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + // Simulating app going to background + NotificationCenter.default.post(name: UIApplication.didEnterBackgroundNotification, object: nil) + + let waitForBackground = XCTestExpectation(description: "Wait for 4 seconds in background.") + DispatchQueue.main.asyncAfter(deadline: .now() + 4) { + // Simulating app returning to foreground + NotificationCenter.default.post(name: UIApplication.didBecomeActiveNotification, object: nil) + + let waitForForeground = XCTestExpectation(description: "Wait for 3 seconds after foregrounding.") + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + waitForForeground.fulfill() + } + self.wait(for: [waitForForeground], timeout: 5.0) + } + self.wait(for: [waitForBackground], timeout: 5.0) } - wait(for: [bgExpectation, fgExpectation], timeout: 15) - XCTAssertNotNil(viewID, "View should handle background and foreground notifications correctly.") + wait(for: [waitForStart], timeout: 6.0) // Wait for the expectation to be fulfilled + } + + + // Helper methods to validate results + private func checkRecordedEventsForView(viewName: String, segmentation: [String: String]? = nil) { + // Implement your logic to check recorded events for the specified view + } + + private func checkRecordedEventsForView(withID viewID: String!, segmentation: [String: String]? = nil) { + // Implement your logic to check recorded events for the specified view ID + } + + private func checkAllViewsStoppedWithSegmentation(_ segmentation: [String: String]) { + // Implement your logic to check that all views have been stopped with specific segmentation + } + + private func checkGlobalSegmentationApplied(expected: [String: String]) { + // Implement your logic to verify global segmentation applied correctly + } + + private func checkNoRecordedEvents() { + // Implement logic to verify no recorded events } } diff --git a/CountlyViewTrackingInternal.m b/CountlyViewTrackingInternal.m index 89af7504..de91ad17 100644 --- a/CountlyViewTrackingInternal.m +++ b/CountlyViewTrackingInternal.m @@ -10,7 +10,7 @@ @interface CountlyViewTrackingInternal () #if (TARGET_OS_IOS || TARGET_OS_TV) @property (nonatomic) NSMutableSet* automaticViewTrackingExclusionList; #endif -@property (nonatomic) NSMutableDictionary * viewDataDictionary; +@property (nonatomic, strong) NSMutableDictionary * viewDataDictionary; @property (nonatomic) NSMutableDictionary* viewSegmentation; @property (nonatomic) BOOL isFirstView; @end