From 236d6608dedfb113e2fe91189cb90e16f5fad852 Mon Sep 17 00:00:00 2001 From: Zihe Jia Date: Thu, 10 Jun 2021 10:38:36 -0700 Subject: [PATCH 1/2] Fix optOutTracking in macOS --- .github/workflows/swift.yml | 3 +- .../MixpanelDemo.xcodeproj/project.pbxproj | 544 +++++++++- .../xcschemes/MixpanelDemoMac.xcscheme | 89 ++ .../MixpanelDemoWatch (Notification).xcscheme | 25 +- .../xcschemes/MixpanelDemoWatch.xcscheme | 25 +- .../MixpanelDemoMac/AppDelegate.swift | 34 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 58 ++ .../Assets.xcassets/Contents.json | 6 + .../Base.lproj/Main.storyboard | 717 ++++++++++++++ MixpanelDemo/MixpanelDemoMac/Info.plist | 32 + .../MixpanelDemoMac.entitlements | 14 + .../MixpanelDemoMac/ViewController.swift | 29 + MixpanelDemo/MixpanelDemoMacTests/Info.plist | 22 + .../MixpanelBaseTests.swift | 135 +++ .../MixpanelDemoMacTests-Bridging-Header.h | 4 + .../MixpanelDemoTests.swift | 937 ++++++++++++++++++ .../MixpanelGroupTests.swift | 141 +++ .../MixpanelOptOutTests.swift | 260 +++++ .../MixpanelPeopleTests.swift | 206 ++++ .../MixpanelDemoMacTests/TestConstants.swift | 55 + .../MixpanelDemoMacUITests/Info.plist | 22 + .../MixpanelDemoMacUITests.swift | 43 + .../MixpanelOptOutTests.swift | 24 +- MixpanelDemo/Podfile | 4 + Sources/Mixpanel.swift | 3 +- Sources/MixpanelInstance.swift | 15 +- Sources/Persistence.swift | 7 +- 28 files changed, 3394 insertions(+), 71 deletions(-) create mode 100644 MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemoMac.xcscheme create mode 100644 MixpanelDemo/MixpanelDemoMac/AppDelegate.swift create mode 100644 MixpanelDemo/MixpanelDemoMac/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 MixpanelDemo/MixpanelDemoMac/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 MixpanelDemo/MixpanelDemoMac/Assets.xcassets/Contents.json create mode 100644 MixpanelDemo/MixpanelDemoMac/Base.lproj/Main.storyboard create mode 100644 MixpanelDemo/MixpanelDemoMac/Info.plist create mode 100644 MixpanelDemo/MixpanelDemoMac/MixpanelDemoMac.entitlements create mode 100644 MixpanelDemo/MixpanelDemoMac/ViewController.swift create mode 100644 MixpanelDemo/MixpanelDemoMacTests/Info.plist create mode 100644 MixpanelDemo/MixpanelDemoMacTests/MixpanelBaseTests.swift create mode 100644 MixpanelDemo/MixpanelDemoMacTests/MixpanelDemoMacTests-Bridging-Header.h create mode 100644 MixpanelDemo/MixpanelDemoMacTests/MixpanelDemoTests.swift create mode 100644 MixpanelDemo/MixpanelDemoMacTests/MixpanelGroupTests.swift create mode 100644 MixpanelDemo/MixpanelDemoMacTests/MixpanelOptOutTests.swift create mode 100644 MixpanelDemo/MixpanelDemoMacTests/MixpanelPeopleTests.swift create mode 100644 MixpanelDemo/MixpanelDemoMacTests/TestConstants.swift create mode 100644 MixpanelDemo/MixpanelDemoMacUITests/Info.plist create mode 100644 MixpanelDemo/MixpanelDemoMacUITests/MixpanelDemoMacUITests.swift diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 9b0db427a..9c69a4087 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -12,6 +12,7 @@ jobs: strategy: matrix: destination: ['OS=14.4,name="iPhone 11"'] + scheme: ['MixpanelDemo', 'MixpanelDemoMac'] steps: - uses: actions/checkout@v2 @@ -25,7 +26,7 @@ jobs: working-directory: MixpanelDemo run: | set -o pipefail - xcodebuild -workspace MixpanelDemo.xcworkspace -scheme MixpanelDemo -derivedDataPath Build/ -destination ${{ matrix.destination }} -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES -enableCodeCoverage YES clean build test | xcpretty -c; + xcodebuild -workspace MixpanelDemo.xcworkspace -scheme ${{ matrix.scheme }} -derivedDataPath Build/ -destination ${{ matrix.destination }} -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES -enableCodeCoverage YES clean build test | xcpretty -c; - name: Pod Lint run: pod lib lint --allow-warnings - name: Code Coverage Report diff --git a/MixpanelDemo/MixpanelDemo.xcodeproj/project.pbxproj b/MixpanelDemo/MixpanelDemo.xcodeproj/project.pbxproj index 521335e91..b4a88007b 100644 --- a/MixpanelDemo/MixpanelDemo.xcodeproj/project.pbxproj +++ b/MixpanelDemo/MixpanelDemo.xcodeproj/project.pbxproj @@ -20,6 +20,19 @@ 860BE3132072B85D00C12F18 /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 860BE30F2072B83E00C12F18 /* CoreTelephony.framework */; }; 860BE3712076FBCC00C12F18 /* GDPRViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 860BE3702076FBCC00C12F18 /* GDPRViewController.swift */; }; 862773E3206B3B1D00CBE79E /* MixpanelOptOutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 862773E2206B3B1D00CBE79E /* MixpanelOptOutTests.swift */; }; + 8654F2C7266ED84F00ACEED5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8654F2C6266ED84F00ACEED5 /* AppDelegate.swift */; }; + 8654F2C9266ED84F00ACEED5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8654F2C8266ED84F00ACEED5 /* ViewController.swift */; }; + 8654F2CB266ED85300ACEED5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8654F2CA266ED85300ACEED5 /* Assets.xcassets */; }; + 8654F2CE266ED85300ACEED5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8654F2CC266ED85300ACEED5 /* Main.storyboard */; }; + 8654F2E5266ED85300ACEED5 /* MixpanelDemoMacUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8654F2E4266ED85300ACEED5 /* MixpanelDemoMacUITests.swift */; }; + 8654F2F1266ED86100ACEED5 /* Mixpanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E12CE4161E9E1C370023AA2D /* Mixpanel.framework */; }; + 8654F2F2266ED86100ACEED5 /* Mixpanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E12CE4161E9E1C370023AA2D /* Mixpanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 8654F2FD2671636B00ACEED5 /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8654F2F82671636A00ACEED5 /* TestConstants.swift */; }; + 8654F2FE2671636B00ACEED5 /* MixpanelDemoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8654F2F92671636A00ACEED5 /* MixpanelDemoTests.swift */; }; + 8654F2FF2671636B00ACEED5 /* MixpanelBaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8654F2FA2671636A00ACEED5 /* MixpanelBaseTests.swift */; }; + 8654F3002671636B00ACEED5 /* MixpanelPeopleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8654F2FB2671636B00ACEED5 /* MixpanelPeopleTests.swift */; }; + 8654F3012671636B00ACEED5 /* MixpanelOptOutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8654F2FC2671636B00ACEED5 /* MixpanelOptOutTests.swift */; }; + 8654F3052671C4B000ACEED5 /* MixpanelGroupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8654F3032671C4B000ACEED5 /* MixpanelGroupTests.swift */; }; 86F86E8F22440C5D00B69832 /* Interface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 86F86E8D22440C5C00B69832 /* Interface.storyboard */; }; 86F86E9122440C5F00B69832 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 86F86E9022440C5F00B69832 /* Assets.xcassets */; }; 86F86E9822440C5F00B69832 /* MixpanelDemoWatch Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 86F86E9722440C5F00B69832 /* MixpanelDemoWatch Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -38,6 +51,7 @@ 86F86F2122460B3300B69832 /* MixpanelDemoTVUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86F86F2022460B3300B69832 /* MixpanelDemoTVUITests.swift */; }; 86F86F2E22460B4F00B69832 /* Mixpanel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E16C35E41D872045003500AC /* Mixpanel.framework */; }; 86F86F2F22460B4F00B69832 /* Mixpanel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E16C35E41D872045003500AC /* Mixpanel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + B4B5DABFC38A904240FE7CE4 /* Pods_MixpanelDemoMacTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B89985A7B7D8CFD708D22619 /* Pods_MixpanelDemoMacTests.framework */; }; E1016E361D3D46DF00075BB7 /* ActionCompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1016E351D3D46DF00075BB7 /* ActionCompleteViewController.swift */; }; E12406201D249B2500383635 /* MixpanelBaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E124061F1D249B2500383635 /* MixpanelBaseTests.swift */; }; E12BD0371D89FE6A008989C9 /* MixpanelCodelessTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12BD0361D89FE6A008989C9 /* MixpanelCodelessTests.swift */; }; @@ -73,6 +87,20 @@ remoteGlobalIDString = 60DDD14623D39620004F7CBB; remoteInfo = NotificationServiceExtension; }; + 8654F2D6266ED85300ACEED5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E15FF7CB1D0461130076CDE3 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8654F2C3266ED84F00ACEED5; + remoteInfo = MixpanelDemoMac; + }; + 8654F2E1266ED85300ACEED5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = E15FF7CB1D0461130076CDE3 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8654F2C3266ED84F00ACEED5; + remoteInfo = MixpanelDemoMac; + }; 86F86E9922440C5F00B69832 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = E15FF7CB1D0461130076CDE3 /* Project object */; @@ -171,6 +199,17 @@ name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; }; + 8654F2F3266ED86100ACEED5 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 8654F2F2266ED86100ACEED5 /* Mixpanel.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; 86F86EB322440C6000B69832 /* Embed App Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -240,9 +279,29 @@ 60DDD14B23D39621004F7CBB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 671EECAE21432E5F006DD9FA /* GroupsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupsViewController.swift; sourceTree = ""; }; 673ABE4221405A0500B1784B /* MixpanelGroupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MixpanelGroupTests.swift; sourceTree = ""; }; + 7152EAD3B58F19171490337E /* Pods-MixpanelDemoMacTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MixpanelDemoMacTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-MixpanelDemoMacTests/Pods-MixpanelDemoMacTests.release.xcconfig"; sourceTree = ""; }; 860BE30F2072B83E00C12F18 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; }; 860BE3702076FBCC00C12F18 /* GDPRViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GDPRViewController.swift; sourceTree = ""; }; 862773E2206B3B1D00CBE79E /* MixpanelOptOutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MixpanelOptOutTests.swift; sourceTree = ""; }; + 8654F2C4266ED84F00ACEED5 /* MixpanelDemoMac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MixpanelDemoMac.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8654F2C6266ED84F00ACEED5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 8654F2C8266ED84F00ACEED5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 8654F2CA266ED85300ACEED5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 8654F2CD266ED85300ACEED5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 8654F2CF266ED85300ACEED5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 8654F2D0266ED85300ACEED5 /* MixpanelDemoMac.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MixpanelDemoMac.entitlements; sourceTree = ""; }; + 8654F2D5266ED85300ACEED5 /* MixpanelDemoMacTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MixpanelDemoMacTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 8654F2DB266ED85300ACEED5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 8654F2E0266ED85300ACEED5 /* MixpanelDemoMacUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MixpanelDemoMacUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 8654F2E4266ED85300ACEED5 /* MixpanelDemoMacUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MixpanelDemoMacUITests.swift; sourceTree = ""; }; + 8654F2E6266ED85300ACEED5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 8654F2F72671636A00ACEED5 /* MixpanelDemoMacTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MixpanelDemoMacTests-Bridging-Header.h"; sourceTree = ""; }; + 8654F2F82671636A00ACEED5 /* TestConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestConstants.swift; sourceTree = ""; }; + 8654F2F92671636A00ACEED5 /* MixpanelDemoTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MixpanelDemoTests.swift; sourceTree = ""; }; + 8654F2FA2671636A00ACEED5 /* MixpanelBaseTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MixpanelBaseTests.swift; sourceTree = ""; }; + 8654F2FB2671636B00ACEED5 /* MixpanelPeopleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MixpanelPeopleTests.swift; sourceTree = ""; }; + 8654F2FC2671636B00ACEED5 /* MixpanelOptOutTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MixpanelOptOutTests.swift; sourceTree = ""; }; + 8654F3032671C4B000ACEED5 /* MixpanelGroupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MixpanelGroupTests.swift; sourceTree = ""; }; 8684BBDF244E643100A4FD98 /* NotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationServiceExtension.entitlements; sourceTree = ""; }; 86F86E8B22440C5C00B69832 /* MixpanelDemoWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MixpanelDemoWatch.app; sourceTree = BUILT_PRODUCTS_DIR; }; 86F86E8E22440C5C00B69832 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Interface.storyboard; sourceTree = ""; }; @@ -268,6 +327,8 @@ 86F86F2022460B3300B69832 /* MixpanelDemoTVUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MixpanelDemoTVUITests.swift; sourceTree = ""; }; 86F86F2222460B3300B69832 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9233A000BB35A654EEC9A9D6 /* Pods-MixpanelDemoTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MixpanelDemoTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MixpanelDemoTests/Pods-MixpanelDemoTests.debug.xcconfig"; sourceTree = ""; }; + B8470B408BBE5D674A823DCB /* Pods-MixpanelDemoMacTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MixpanelDemoMacTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MixpanelDemoMacTests/Pods-MixpanelDemoMacTests.debug.xcconfig"; sourceTree = ""; }; + B89985A7B7D8CFD708D22619 /* Pods_MixpanelDemoMacTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MixpanelDemoMacTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E1016E351D3D46DF00075BB7 /* ActionCompleteViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionCompleteViewController.swift; sourceTree = ""; }; E124061F1D249B2500383635 /* MixpanelBaseTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MixpanelBaseTests.swift; sourceTree = ""; }; E12BD0361D89FE6A008989C9 /* MixpanelCodelessTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MixpanelCodelessTests.swift; sourceTree = ""; }; @@ -308,6 +369,29 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 8654F2C1266ED84F00ACEED5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8654F2F1266ED86100ACEED5 /* Mixpanel.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8654F2D2266ED85300ACEED5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B4B5DABFC38A904240FE7CE4 /* Pods_MixpanelDemoMacTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8654F2DD266ED85300ACEED5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 86F86E9422440C5F00B69832 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -356,6 +440,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + F3F7F9086F52A38E96D929F8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -364,6 +455,8 @@ children = ( 9233A000BB35A654EEC9A9D6 /* Pods-MixpanelDemoTests.debug.xcconfig */, 5609994314AFCE2B8FF1705B /* Pods-MixpanelDemoTests.release.xcconfig */, + B8470B408BBE5D674A823DCB /* Pods-MixpanelDemoMacTests.debug.xcconfig */, + 7152EAD3B58F19171490337E /* Pods-MixpanelDemoMacTests.release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -384,10 +477,48 @@ 860BE30F2072B83E00C12F18 /* CoreTelephony.framework */, E1C8F50B1EC244E90089C537 /* StoreKit.framework */, 37433CF0620BC6A0A5EFC541 /* Pods_MixpanelDemoTests.framework */, + B89985A7B7D8CFD708D22619 /* Pods_MixpanelDemoMacTests.framework */, ); name = Frameworks; sourceTree = ""; }; + 8654F2C5266ED84F00ACEED5 /* MixpanelDemoMac */ = { + isa = PBXGroup; + children = ( + 8654F2C6266ED84F00ACEED5 /* AppDelegate.swift */, + 8654F2C8266ED84F00ACEED5 /* ViewController.swift */, + 8654F2CA266ED85300ACEED5 /* Assets.xcassets */, + 8654F2CC266ED85300ACEED5 /* Main.storyboard */, + 8654F2CF266ED85300ACEED5 /* Info.plist */, + 8654F2D0266ED85300ACEED5 /* MixpanelDemoMac.entitlements */, + ); + path = MixpanelDemoMac; + sourceTree = ""; + }; + 8654F2D8266ED85300ACEED5 /* MixpanelDemoMacTests */ = { + isa = PBXGroup; + children = ( + 8654F2FA2671636A00ACEED5 /* MixpanelBaseTests.swift */, + 8654F2F92671636A00ACEED5 /* MixpanelDemoTests.swift */, + 8654F3032671C4B000ACEED5 /* MixpanelGroupTests.swift */, + 8654F2FC2671636B00ACEED5 /* MixpanelOptOutTests.swift */, + 8654F2FB2671636B00ACEED5 /* MixpanelPeopleTests.swift */, + 8654F2F82671636A00ACEED5 /* TestConstants.swift */, + 8654F2DB266ED85300ACEED5 /* Info.plist */, + 8654F2F72671636A00ACEED5 /* MixpanelDemoMacTests-Bridging-Header.h */, + ); + path = MixpanelDemoMacTests; + sourceTree = ""; + }; + 8654F2E3266ED85300ACEED5 /* MixpanelDemoMacUITests */ = { + isa = PBXGroup; + children = ( + 8654F2E4266ED85300ACEED5 /* MixpanelDemoMacUITests.swift */, + 8654F2E6266ED85300ACEED5 /* Info.plist */, + ); + path = MixpanelDemoMacUITests; + sourceTree = ""; + }; 86F86E8C22440C5C00B69832 /* MixpanelDemoWatch */ = { isa = PBXGroup; children = ( @@ -476,6 +607,9 @@ 86F86F1422460B3200B69832 /* MixpanelDemoTVTests */, 86F86F1F22460B3200B69832 /* MixpanelDemoTVUITests */, 60DDD14823D39620004F7CBB /* NotificationServiceExtension */, + 8654F2C5266ED84F00ACEED5 /* MixpanelDemoMac */, + 8654F2D8266ED85300ACEED5 /* MixpanelDemoMacTests */, + 8654F2E3266ED85300ACEED5 /* MixpanelDemoMacUITests */, E15FF7D41D0461130076CDE3 /* Products */, 28127E0746D6CC818BA7B834 /* Pods */, 773080F90ECB3A0D67A6E636 /* Frameworks */, @@ -493,6 +627,9 @@ 86F86F1122460B3200B69832 /* MixpanelDemoTVTests.xctest */, 86F86F1C22460B3200B69832 /* MixpanelDemoTVUITests.xctest */, 60DDD14723D39620004F7CBB /* NotificationServiceExtension.appex */, + 8654F2C4266ED84F00ACEED5 /* MixpanelDemoMac.app */, + 8654F2D5266ED85300ACEED5 /* MixpanelDemoMacTests.xctest */, + 8654F2E0266ED85300ACEED5 /* MixpanelDemoMacUITests.xctest */, ); name = Products; sourceTree = ""; @@ -568,12 +705,69 @@ productReference = 60DDD14723D39620004F7CBB /* NotificationServiceExtension.appex */; productType = "com.apple.product-type.app-extension"; }; + 8654F2C3266ED84F00ACEED5 /* MixpanelDemoMac */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8654F2EE266ED85300ACEED5 /* Build configuration list for PBXNativeTarget "MixpanelDemoMac" */; + buildPhases = ( + 8654F2C0266ED84F00ACEED5 /* Sources */, + 8654F2C1266ED84F00ACEED5 /* Frameworks */, + 8654F2C2266ED84F00ACEED5 /* Resources */, + 8654F2F3266ED86100ACEED5 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = MixpanelDemoMac; + productName = MixpanelDemoMac; + productReference = 8654F2C4266ED84F00ACEED5 /* MixpanelDemoMac.app */; + productType = "com.apple.product-type.application"; + }; + 8654F2D4266ED85300ACEED5 /* MixpanelDemoMacTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8654F2EF266ED85300ACEED5 /* Build configuration list for PBXNativeTarget "MixpanelDemoMacTests" */; + buildPhases = ( + 8DD48666EFFBB25A9B0B3EB9 /* [CP] Check Pods Manifest.lock */, + 8654F2D1266ED85300ACEED5 /* Sources */, + 8654F2D2266ED85300ACEED5 /* Frameworks */, + 8654F2D3266ED85300ACEED5 /* Resources */, + D0A31031221AEE02943CC8F4 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 8654F2D7266ED85300ACEED5 /* PBXTargetDependency */, + ); + name = MixpanelDemoMacTests; + productName = MixpanelDemoMacTests; + productReference = 8654F2D5266ED85300ACEED5 /* MixpanelDemoMacTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 8654F2DF266ED85300ACEED5 /* MixpanelDemoMacUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 8654F2F0266ED85300ACEED5 /* Build configuration list for PBXNativeTarget "MixpanelDemoMacUITests" */; + buildPhases = ( + 8654F2DC266ED85300ACEED5 /* Sources */, + 8654F2DD266ED85300ACEED5 /* Frameworks */, + 8654F2DE266ED85300ACEED5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 8654F2E2266ED85300ACEED5 /* PBXTargetDependency */, + ); + name = MixpanelDemoMacUITests; + productName = MixpanelDemoMacUITests; + productReference = 8654F2E0266ED85300ACEED5 /* MixpanelDemoMacUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; 86F86E8A22440C5C00B69832 /* MixpanelDemoWatch */ = { isa = PBXNativeTarget; buildConfigurationList = 86F86EB422440C6000B69832 /* Build configuration list for PBXNativeTarget "MixpanelDemoWatch" */; buildPhases = ( 86F86E8922440C5C00B69832 /* Resources */, 86F86EB322440C6000B69832 /* Embed App Extensions */, + F3F7F9086F52A38E96D929F8 /* Frameworks */, ); buildRules = ( ); @@ -708,7 +902,7 @@ E15FF7CB1D0461130076CDE3 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1120; + LastSwiftUpdateCheck = 1250; LastUpgradeCheck = 1200; ORGANIZATIONNAME = Mixpanel; TargetAttributes = { @@ -717,6 +911,18 @@ DevelopmentTeam = E8FVX7QLET; ProvisioningStyle = Automatic; }; + 8654F2C3266ED84F00ACEED5 = { + CreatedOnToolsVersion = 12.5; + }; + 8654F2D4266ED85300ACEED5 = { + CreatedOnToolsVersion = 12.5; + LastSwiftMigration = 1250; + TestTargetID = 8654F2C3266ED84F00ACEED5; + }; + 8654F2DF266ED85300ACEED5 = { + CreatedOnToolsVersion = 12.5; + TestTargetID = 8654F2C3266ED84F00ACEED5; + }; 86F86E8A22440C5C00B69832 = { CreatedOnToolsVersion = 10.1; DevelopmentTeam = E8FVX7QLET; @@ -797,6 +1003,9 @@ 86F86F1022460B3200B69832 /* MixpanelDemoTVTests */, 86F86F1B22460B3200B69832 /* MixpanelDemoTVUITests */, 60DDD14623D39620004F7CBB /* NotificationServiceExtension */, + 8654F2C3266ED84F00ACEED5 /* MixpanelDemoMac */, + 8654F2D4266ED85300ACEED5 /* MixpanelDemoMacTests */, + 8654F2DF266ED85300ACEED5 /* MixpanelDemoMacUITests */, ); }; /* End PBXProject section */ @@ -840,6 +1049,29 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 8654F2C2266ED84F00ACEED5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8654F2CB266ED85300ACEED5 /* Assets.xcassets in Resources */, + 8654F2CE266ED85300ACEED5 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8654F2D3266ED85300ACEED5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8654F2DE266ED85300ACEED5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 86F86E8922440C5C00B69832 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -929,8 +1161,48 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-MixpanelDemoTests/Pods-MixpanelDemoTests-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/Nocilla/Nocilla.framework", + "${PODS_ROOT}/Target Support Files/Pods-MixpanelDemoTests/Pods-MixpanelDemoTests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Nocilla-iOS/Nocilla.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nocilla.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MixpanelDemoTests/Pods-MixpanelDemoTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 8DD48666EFFBB25A9B0B3EB9 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-MixpanelDemoMacTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + D0A31031221AEE02943CC8F4 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-MixpanelDemoMacTests/Pods-MixpanelDemoMacTests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Nocilla-macOS/Nocilla.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -938,7 +1210,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MixpanelDemoTests/Pods-MixpanelDemoTests-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MixpanelDemoMacTests/Pods-MixpanelDemoMacTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -952,6 +1224,36 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 8654F2C0266ED84F00ACEED5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8654F2C9266ED84F00ACEED5 /* ViewController.swift in Sources */, + 8654F2C7266ED84F00ACEED5 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8654F2D1266ED85300ACEED5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8654F2FF2671636B00ACEED5 /* MixpanelBaseTests.swift in Sources */, + 8654F3052671C4B000ACEED5 /* MixpanelGroupTests.swift in Sources */, + 8654F2FE2671636B00ACEED5 /* MixpanelDemoTests.swift in Sources */, + 8654F3002671636B00ACEED5 /* MixpanelPeopleTests.swift in Sources */, + 8654F2FD2671636B00ACEED5 /* TestConstants.swift in Sources */, + 8654F3012671636B00ACEED5 /* MixpanelOptOutTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8654F2DC266ED85300ACEED5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8654F2E5266ED85300ACEED5 /* MixpanelDemoMacUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 86F86E9322440C5F00B69832 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1031,6 +1333,16 @@ target = 60DDD14623D39620004F7CBB /* NotificationServiceExtension */; targetProxy = 60DDD14C23D39621004F7CBB /* PBXContainerItemProxy */; }; + 8654F2D7266ED85300ACEED5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8654F2C3266ED84F00ACEED5 /* MixpanelDemoMac */; + targetProxy = 8654F2D6266ED85300ACEED5 /* PBXContainerItemProxy */; + }; + 8654F2E2266ED85300ACEED5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8654F2C3266ED84F00ACEED5 /* MixpanelDemoMac */; + targetProxy = 8654F2E1266ED85300ACEED5 /* PBXContainerItemProxy */; + }; 86F86E9A22440C5F00B69832 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 86F86E9622440C5F00B69832 /* MixpanelDemoWatch Extension */; @@ -1075,6 +1387,14 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 8654F2CC266ED85300ACEED5 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 8654F2CD266ED85300ACEED5 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; 86F86E8D22440C5C00B69832 /* Interface.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -1180,6 +1500,195 @@ }; name = Release; }; + 8654F2E7266ED85300ACEED5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = MixpanelDemoMac/MixpanelDemoMac.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = MixpanelDemoMac/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.mixpanel.MixpanelDemoMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 8654F2E8266ED85300ACEED5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_ENTITLEMENTS = MixpanelDemoMac/MixpanelDemoMac.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = MixpanelDemoMac/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.2; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.mixpanel.MixpanelDemoMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 8654F2E9266ED85300ACEED5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B8470B408BBE5D674A823DCB /* Pods-MixpanelDemoMacTests.debug.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = MixpanelDemoMacTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.mixpanel.MixpanelDemoMacTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OBJC_BRIDGING_HEADER = "MixpanelDemoMacTests/MixpanelDemoMacTests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MixpanelDemoMac.app/Contents/MacOS/MixpanelDemoMac"; + }; + name = Debug; + }; + 8654F2EA266ED85300ACEED5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7152EAD3B58F19171490337E /* Pods-MixpanelDemoMacTests.release.xcconfig */; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = MixpanelDemoMacTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.2; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.mixpanel.MixpanelDemoMacTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OBJC_BRIDGING_HEADER = "MixpanelDemoMacTests/MixpanelDemoMacTests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MixpanelDemoMac.app/Contents/MacOS/MixpanelDemoMac"; + }; + name = Release; + }; + 8654F2EB266ED85300ACEED5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = MixpanelDemoMacUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.mixpanel.MixpanelDemoMacUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = MixpanelDemoMac; + }; + name = Debug; + }; + 8654F2EC266ED85300ACEED5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = MixpanelDemoMacUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.2; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.mixpanel.MixpanelDemoMacUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + TEST_TARGET_NAME = MixpanelDemoMac; + }; + name = Release; + }; 86F86EA922440C6000B69832 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1736,6 +2245,33 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 8654F2EE266ED85300ACEED5 /* Build configuration list for PBXNativeTarget "MixpanelDemoMac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8654F2E7266ED85300ACEED5 /* Debug */, + 8654F2E8266ED85300ACEED5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8654F2EF266ED85300ACEED5 /* Build configuration list for PBXNativeTarget "MixpanelDemoMacTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8654F2E9266ED85300ACEED5 /* Debug */, + 8654F2EA266ED85300ACEED5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 8654F2F0266ED85300ACEED5 /* Build configuration list for PBXNativeTarget "MixpanelDemoMacUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 8654F2EB266ED85300ACEED5 /* Debug */, + 8654F2EC266ED85300ACEED5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 86F86EB222440C6000B69832 /* Build configuration list for PBXNativeTarget "MixpanelDemoWatch Extension" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemoMac.xcscheme b/MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemoMac.xcscheme new file mode 100644 index 000000000..60d7b360a --- /dev/null +++ b/MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemoMac.xcscheme @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemoWatch (Notification).xcscheme b/MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemoWatch (Notification).xcscheme index 5f7053e0e..3b4cf26ea 100644 --- a/MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemoWatch (Notification).xcscheme +++ b/MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemoWatch (Notification).xcscheme @@ -56,10 +56,8 @@ allowLocationSimulation = "YES" launchAutomaticallySubstyle = "8" notificationPayloadFile = "MixpanelDemoWatch Extension/PushNotificationPayload.apns"> - + - + - + - - - - - + diff --git a/MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemoWatch.xcscheme b/MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemoWatch.xcscheme index 6b3b87049..1422e45dc 100644 --- a/MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemoWatch.xcscheme +++ b/MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemoWatch.xcscheme @@ -55,10 +55,8 @@ debugServiceExtension = "internal" allowLocationSimulation = "YES" notificationPayloadFile = "MixpanelDemoWatch Extension/PushNotificationPayload.apns"> - + - + - + - - - - - + diff --git a/MixpanelDemo/MixpanelDemoMac/AppDelegate.swift b/MixpanelDemo/MixpanelDemoMac/AppDelegate.swift new file mode 100644 index 000000000..35331d2a5 --- /dev/null +++ b/MixpanelDemo/MixpanelDemoMac/AppDelegate.swift @@ -0,0 +1,34 @@ +// +// AppDelegate.swift +// MixpanelDemoMac +// +// Created by ZIHE JIA on 6/7/21. +// Copyright © 2021 Mixpanel. All rights reserved. +// + +import Cocoa +import Mixpanel + +@main +class AppDelegate: NSObject, NSApplicationDelegate { + + + + + func applicationDidFinishLaunching(_ aNotification: Notification) { + // Insert code here to initialize your application + + var ADD_YOUR_MIXPANEL_TOKEN_BELOW_🛠🛠🛠🛠🛠🛠: String + + Mixpanel.initialize(token: "MIXPANEL_TOKEN") + Mixpanel.mainInstance().loggingEnabled = true + Mixpanel.mainInstance().track("Tracked Event") + } + + func applicationWillTerminate(_ aNotification: Notification) { + // Insert code here to tear down your application + } + + +} + diff --git a/MixpanelDemo/MixpanelDemoMac/Assets.xcassets/AccentColor.colorset/Contents.json b/MixpanelDemo/MixpanelDemoMac/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/MixpanelDemo/MixpanelDemoMac/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MixpanelDemo/MixpanelDemoMac/Assets.xcassets/AppIcon.appiconset/Contents.json b/MixpanelDemo/MixpanelDemoMac/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..3f00db43e --- /dev/null +++ b/MixpanelDemo/MixpanelDemoMac/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MixpanelDemo/MixpanelDemoMac/Assets.xcassets/Contents.json b/MixpanelDemo/MixpanelDemoMac/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/MixpanelDemo/MixpanelDemoMac/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/MixpanelDemo/MixpanelDemoMac/Base.lproj/Main.storyboard b/MixpanelDemo/MixpanelDemoMac/Base.lproj/Main.storyboard new file mode 100644 index 000000000..23dd3fc96 --- /dev/null +++ b/MixpanelDemo/MixpanelDemoMac/Base.lproj/Main.storyboard @@ -0,0 +1,717 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MixpanelDemo/MixpanelDemoMac/Info.plist b/MixpanelDemo/MixpanelDemoMac/Info.plist new file mode 100644 index 000000000..52a48e896 --- /dev/null +++ b/MixpanelDemo/MixpanelDemoMac/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + Copyright © 2021 Mixpanel. All rights reserved. + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + + diff --git a/MixpanelDemo/MixpanelDemoMac/MixpanelDemoMac.entitlements b/MixpanelDemo/MixpanelDemoMac/MixpanelDemoMac.entitlements new file mode 100644 index 000000000..40b639e46 --- /dev/null +++ b/MixpanelDemo/MixpanelDemoMac/MixpanelDemoMac.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + com.apple.security.network.client + + com.apple.security.network.server + + + diff --git a/MixpanelDemo/MixpanelDemoMac/ViewController.swift b/MixpanelDemo/MixpanelDemoMac/ViewController.swift new file mode 100644 index 000000000..797ccc3b8 --- /dev/null +++ b/MixpanelDemo/MixpanelDemoMac/ViewController.swift @@ -0,0 +1,29 @@ +// +// ViewController.swift +// MixpanelDemoMac +// +// Created by ZIHE JIA on 6/7/21. +// Copyright © 2021 Mixpanel. All rights reserved. +// + +import Cocoa +import Mixpanel + + +class ViewController: NSViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + override var representedObject: Any? { + didSet { + // Update the view, if already loaded. + } + } + + +} + diff --git a/MixpanelDemo/MixpanelDemoMacTests/Info.plist b/MixpanelDemo/MixpanelDemoMacTests/Info.plist new file mode 100644 index 000000000..64d65ca49 --- /dev/null +++ b/MixpanelDemo/MixpanelDemoMacTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/MixpanelDemo/MixpanelDemoMacTests/MixpanelBaseTests.swift b/MixpanelDemo/MixpanelDemoMacTests/MixpanelBaseTests.swift new file mode 100644 index 000000000..200c4168c --- /dev/null +++ b/MixpanelDemo/MixpanelDemoMacTests/MixpanelBaseTests.swift @@ -0,0 +1,135 @@ +// +// MixpanelBaseTests.swift +// MixpanelDemo +// +// Created by Yarden Eitan on 6/29/16. +// Copyright © 2016 Mixpanel. All rights reserved. +// + +import XCTest +import Nocilla + +@testable import Mixpanel +@testable import MixpanelDemoMac + +class MixpanelBaseTests: XCTestCase, MixpanelDelegate { + var mixpanel: MixpanelInstance! + var mixpanelWillFlush: Bool! + static var requestCount = 0 + + override func setUp() { + NSLog("starting test setup...") + super.setUp() + stubTrack() + stubDecide() + stubEngage() + stubGroups() + LSNocilla.sharedInstance().start() + mixpanelWillFlush = false + mixpanel = Mixpanel.initialize(token: kTestToken) + mixpanel.reset() + waitForTrackingQueue() + + NSLog("finished test setup") + } + + override func tearDown() { + super.tearDown() + stubTrack() + stubDecide() + stubEngage() + stubGroups() + deleteOptOutSettings(mixpanelInstance: mixpanel) + mixpanel.reset() + waitForTrackingQueue() + + LSNocilla.sharedInstance().stop() + LSNocilla.sharedInstance().clearStubs() + + mixpanel = nil + } + + func deleteOptOutSettings(mixpanelInstance: MixpanelInstance) + { + let filePath = Persistence.filePathWithType(.optOutStatus, token: mixpanelInstance.apiToken) + do { + try FileManager.default.removeItem(atPath: filePath!) + } catch { + Logger.info(message: "Unable to remove file at path: \(filePath!)") + } + } + + func mixpanelWillFlush(_ mixpanel: MixpanelInstance) -> Bool { + return mixpanelWillFlush + } + + func waitForTrackingQueue() { + mixpanel.trackingQueue.sync() { + return + } + } + + func randomId() -> String + { + return String(format: "%08x%08x", arc4random(), arc4random()) + } + + func waitForMixpanelQueues() { + mixpanel.trackingQueue.sync() { + mixpanel.networkQueue.sync() { + return + } + } + } + + func waitForNetworkQueue() { + mixpanel.networkQueue.sync() { + return + } + } + + func waitForAsyncTasks() { + var hasCompletedTask = false + DispatchQueue.main.async { + hasCompletedTask = true + } + + let loopUntil = Date(timeIntervalSinceNow: 10) + while !hasCompletedTask && loopUntil.timeIntervalSinceNow > 0 { + RunLoop.current.run(mode: RunLoop.Mode.default, before: loopUntil) + } + } + + func flushAndWaitForNetworkQueue() { + mixpanel.flush() + waitForMixpanelQueues() + } + + func assertDefaultPeopleProperties(_ properties: InternalProperties) { + XCTAssertNotNil(properties["$ios_device_model"], "missing $ios_device_model property") + XCTAssertNotNil(properties["$ios_lib_version"], "missing $ios_lib_version property") + XCTAssertNotNil(properties["$ios_version"], "missing $ios_version property") + XCTAssertNotNil(properties["$ios_app_version"], "missing $ios_app_version property") + XCTAssertNotNil(properties["$ios_app_release"], "missing $ios_app_release property") + } + + func allPropertyTypes() -> Properties { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss zzz" + let date = dateFormatter.date(from: "2012-09-28 19:14:36 PDT") + let nested = ["p1": ["p2": ["p3": ["bottom"]]]] + let opt: String? = nil + return ["string": "yello", + "number": 3, + "date": date!, + "dictionary": ["k": "v", "opt": opt as Any], + "array": ["1", opt as Any], + "null": NSNull(), + "nested": nested, + "url": URL(string: "https://mixpanel.com/")!, + "float": 1.3, + "optional": opt, + ] + } + +} diff --git a/MixpanelDemo/MixpanelDemoMacTests/MixpanelDemoMacTests-Bridging-Header.h b/MixpanelDemo/MixpanelDemoMacTests/MixpanelDemoMacTests-Bridging-Header.h new file mode 100644 index 000000000..1b2cb5d6d --- /dev/null +++ b/MixpanelDemo/MixpanelDemoMacTests/MixpanelDemoMacTests-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/MixpanelDemo/MixpanelDemoMacTests/MixpanelDemoTests.swift b/MixpanelDemo/MixpanelDemoMacTests/MixpanelDemoTests.swift new file mode 100644 index 000000000..bdafbff7e --- /dev/null +++ b/MixpanelDemo/MixpanelDemoMacTests/MixpanelDemoTests.swift @@ -0,0 +1,937 @@ +// +// MixpanelDemoTests.swift +// MixpanelDemoTests +// +// Created by Yarden Eitan on 6/5/16. +// Copyright © 2016 Mixpanel. All rights reserved. +// + +import XCTest +import Nocilla + +@testable import Mixpanel +@testable import MixpanelDemoMac + +class MixpanelDemoTests: MixpanelBaseTests { + + func test5XXResponse() { + LSNocilla.sharedInstance().clearStubs() + _ = stubTrack().andReturn(503) + + mixpanel.track(event: "Fake Event") + + waitForTrackingQueue() + flushAndWaitForNetworkQueue() + flushAndWaitForNetworkQueue() + // Failure count should be 3 + let waitTime = + mixpanel.flushInstance.flushRequest.networkRequestsAllowedAfterTime - Date().timeIntervalSince1970 + print("Delta wait time is \(waitTime)") + XCTAssert(waitTime >= 110, "Network backoff time is less than 2 minutes.") + XCTAssert(mixpanel.flushInstance.flushRequest.networkConsecutiveFailures == 2, + "Network failures did not equal 2") + XCTAssert(mixpanel.eventsQueue.count == 1, + "Removed an event from the queue that was not sent") + } + + func testRetryAfterHTTPHeader() { + LSNocilla.sharedInstance().clearStubs() + _ = stubTrack().andReturn(200)?.withHeader("Retry-After", "60") + + mixpanel.track(event: "Fake Event") + + waitForTrackingQueue() + flushAndWaitForNetworkQueue() + + // Failure count should be 3 + let waitTime = + mixpanel.flushInstance.flushRequest.networkRequestsAllowedAfterTime - Date().timeIntervalSince1970 + print("Delta wait time is \(waitTime)") + XCTAssert(fabs(60 - waitTime) < 5, "Mixpanel did not respect 'Retry-After' HTTP header") + XCTAssert(mixpanel.flushInstance.flushRequest.networkConsecutiveFailures == 0, + "Network failures did not equal 0") + } + + func testFlushEvents() { + stubTrack() + + mixpanel.identify(distinctId: "d1") + for i in 0..<50 { + mixpanel.track(event: "event \(i)") + } + waitForTrackingQueue() + flushAndWaitForNetworkQueue() + XCTAssertTrue(mixpanel.eventsQueue.isEmpty, + "events should have been flushed") + + for i in 0..<60 { + mixpanel.track(event: "event \(i)") + } + waitForTrackingQueue() + flushAndWaitForNetworkQueue() + XCTAssertTrue(mixpanel.eventsQueue.isEmpty, + "events should have been flushed") + } + + + func testFlushPeople() { + stubEngage() + mixpanel.identify(distinctId: "d1") + for i in 0..<50 { + mixpanel.people.set(property: "p1", to: "\(i)") + } + waitForTrackingQueue() + flushAndWaitForNetworkQueue() + XCTAssertTrue(mixpanel.people.peopleQueue.isEmpty, "people should have been flushed") + for i in 0..<60 { + mixpanel.people.set(property: "p1", to: "\(i)") + } + waitForTrackingQueue() + flushAndWaitForNetworkQueue() + XCTAssertTrue(mixpanel.people.peopleQueue.isEmpty, "people should have been flushed") + } + + func testFlushGroups() { + stubGroups() + mixpanel.identify(distinctId: "d1") + let groupKey = "test_key" + let groupValue = "test_value" + for i in 0..<50 { + mixpanel.getGroup(groupKey: groupKey, groupID: groupValue).set(property: "p1", to: "\(i)") + } + waitForTrackingQueue() + flushAndWaitForNetworkQueue() + XCTAssertTrue(mixpanel.groupsQueue.isEmpty, "groups should have been flushed") + for i in 0..<60 { + mixpanel.getGroup(groupKey: groupKey, groupID: groupValue).set(property: "p1", to: "\(i)") + } + waitForTrackingQueue() + flushAndWaitForNetworkQueue() + XCTAssertTrue(mixpanel.people.peopleQueue.isEmpty, "groups should have been flushed") + } + + func testFlushNetworkFailure() { + LSNocilla.sharedInstance().clearStubs() + stubTrack().andFailWithError( + NSError(domain: "com.mixpanel.sdk.testing", code: 1, userInfo: nil)) + for i in 0..<50 { + mixpanel.track(event: "event \(UInt(i))") + } + waitForTrackingQueue() + XCTAssertTrue(mixpanel.eventsQueue.count == 50, "50 events should be queued up") + flushAndWaitForNetworkQueue() + XCTAssertTrue(mixpanel.eventsQueue.count == 50, + "events should still be in the queue if flush fails") + + } + + func testFlushQueueContainsCorruptedEvent() { + stubTrack() + mixpanel.eventsQueue.append(["event": "bad event1", "properties": ["BadProp": Double.nan]]) + mixpanel.eventsQueue.append(["event": "bad event2", "properties": ["BadProp": Float.nan]]) + mixpanel.eventsQueue.append(["event": "bad event3", "properties": ["BadProp": Double.infinity]]) + mixpanel.eventsQueue.append(["event": "bad event4", "properties": ["BadProp": Float.infinity]]) + + for i in 0..<10 { + mixpanel.track(event: "event \(UInt(i))") + } + waitForTrackingQueue() + flushAndWaitForNetworkQueue() + XCTAssertTrue(mixpanel.eventsQueue.count == 0, "good events should still be flushed") + } + + func testAddEventContainsInvalidJsonObjectDoubleNaN() { + stubTrack() + XCTExpectAssert("unsupported property type was allowed") { + mixpanel.track(event: "bad event", properties: ["BadProp": Double.nan]) + } + } + + func testAddEventContainsInvalidJsonObjectFloatNaN() { + XCTExpectAssert("unsupported property type was allowed") { + mixpanel.track(event: "bad event", properties: ["BadProp": Float.nan]) + } + } + + func testAddEventContainsInvalidJsonObjectDoubleInfinity() { + XCTExpectAssert("unsupported property type was allowed") { + mixpanel.track(event: "bad event", properties: ["BadProp": Double.infinity]) + } + } + + func testAddEventContainsInvalidJsonObjectFloatInfinity() { + XCTExpectAssert("unsupported property type was allowed") { + mixpanel.track(event: "bad event", properties: ["BadProp": Float.infinity]) + } + } + + func testAddingEventsAfterFlush() { + stubTrack() + for i in 0..<10 { + mixpanel.track(event: "event \(UInt(i))") + } + waitForTrackingQueue() + XCTAssertTrue(mixpanel.eventsQueue.count == 10, "10 events should be queued up") + flushAndWaitForNetworkQueue() + for i in 0..<5 { + mixpanel.track(event: "event \(UInt(i))") + } + waitForTrackingQueue() + XCTAssertTrue(mixpanel.eventsQueue.count == 5, "5 more events should be queued up") + flushAndWaitForNetworkQueue() + XCTAssertTrue(mixpanel.eventsQueue.isEmpty, "events should have been flushed") + } + + func testDropEvents() { + mixpanel.delegate = self + var events = Queue() + for i in 0..<5000 { + events.append(["i": i]) + } + mixpanel.eventsQueue = events + waitForTrackingQueue() + XCTAssertTrue(mixpanel.eventsQueue.count == 5000) + for i in 0..<5 { + mixpanel.track(event: "event", properties: ["i": 5000 + i]) + } + waitForTrackingQueue() + let e: InternalProperties = mixpanel.eventsQueue.last! + XCTAssertTrue(mixpanel.eventsQueue.count == 5000) + XCTAssertEqual((e["properties"] as? InternalProperties)?["i"] as? Int, 5004) + } + + func testIdentify() { + stubTrack() + stubEngage() + for _ in 0..<2 { + // run this twice to test reset works correctly wrt to distinct ids + let distinctId: String = "d1" + // try this for ODIN and nil + #if MIXPANEL_UNIQUE_DISTINCT_ID + XCTAssertEqual(mixpanel.distinctId, + mixpanel.defaultDistinctId(), + "mixpanel identify failed to set default distinct id") + XCTAssertEqual(mixpanel.anonymousId, + mixpanel.defaultDistinctId(), + "mixpanel failed to set default anonymous id") + #endif + XCTAssertNil(mixpanel.people.distinctId, + "mixpanel people distinct id should default to nil") + XCTAssertNil(mixpanel.people.distinctId, + "mixpanel user id should default to nil") + mixpanel.track(event: "e1") + waitForTrackingQueue() + XCTAssertTrue(mixpanel.eventsQueue.count == 1, + "events should be sent right away with default distinct id") + #if MIXPANEL_UNIQUE_DISTINCT_ID + XCTAssertEqual((mixpanel.eventsQueue.last?["properties"] as? InternalProperties)?["distinct_id"] as? String, + mixpanel.defaultDistinctId(), + "events should use default distinct id if none set") + #endif + XCTAssertEqual((mixpanel.eventsQueue.last?["properties"] as? InternalProperties)?["$lib_version"] as? String, + AutomaticProperties.libVersion(), + "events should has lib version in internal properties") + mixpanel.people.set(property: "p1", to: "a") + waitForTrackingQueue() + XCTAssertTrue(mixpanel.people.peopleQueue.isEmpty, + "people records should go to unidentified queue before identify:") + XCTAssertTrue(mixpanel.people.unidentifiedQueue.count == 1, + "unidentified people records not queued") + XCTAssertEqual(mixpanel.people.unidentifiedQueue.last?["$token"] as? String, + kTestToken, + "incorrect project token in people record") + let anonymousId = mixpanel.anonymousId + mixpanel.identify(distinctId: distinctId) + waitForTrackingQueue() + XCTAssertEqual(mixpanel.distinctId, distinctId, + "mixpanel identify failed to set distinct id") + XCTAssertEqual(mixpanel.userId, distinctId, + "mixpanel identify failed to set user id") + XCTAssertEqual(mixpanel.anonymousId, anonymousId, + "mixpanel identify shouldn't change anonymousId") + XCTAssertEqual(mixpanel.people.distinctId, distinctId, + "mixpanel identify failed to set people distinct id") + XCTAssertTrue(mixpanel.people.unidentifiedQueue.isEmpty, + "identify: should move records from unidentified queue") + XCTAssertTrue(mixpanel.people.peopleQueue.count == 1, + "identify: should move records to main people queue") + XCTAssertEqual(mixpanel.people.peopleQueue.last?["$token"] as? String, + kTestToken, "incorrect project token in people record") + XCTAssertEqual(mixpanel.people.peopleQueue.last?["$distinct_id"] as? String, + distinctId, "distinct id not set properly on unidentified people record") + let p: InternalProperties = mixpanel.people.peopleQueue.last?["$set"] as! InternalProperties + XCTAssertEqual(p["p1"] as? String, "a", "custom people property not queued") + assertDefaultPeopleProperties(p) + mixpanel.people.set(property: "p1", to: "a") + waitForTrackingQueue() + XCTAssertTrue(mixpanel.people.unidentifiedQueue.isEmpty, + "once idenitfy: is called, unidentified queue should be skipped") + XCTAssertTrue(mixpanel.people.peopleQueue.count == 2, + "once identify: is called, records should go straight to main queue") + mixpanel.track(event: "e2") + waitForTrackingQueue() + let newDistinctId = (mixpanel.eventsQueue.last?["properties"] as? InternalProperties)?["distinct_id"] as? String + XCTAssertEqual(newDistinctId, distinctId, + "events should use new distinct id after identify:") + mixpanel.reset() + waitForTrackingQueue() + } + } + + func testIdentifyTrack() { + stubTrack() + stubEngage() + + let distinctIdBeforeIdentify: String? = mixpanel.distinctId + let distinctId = "testIdentifyTrack" + + mixpanel.identify(distinctId: distinctId) + mixpanel.identify(distinctId: distinctId) + waitForTrackingQueue() + waitForTrackingQueue() + + let e: InternalProperties = mixpanel.eventsQueue.last! + XCTAssertEqual(e["event"] as? String, "$identify", "incorrect event name") + let p: InternalProperties = e["properties"] as! InternalProperties + XCTAssertEqual(p["distinct_id"] as? String, distinctId, "wrong distinct_id") + XCTAssertEqual(p["$anon_distinct_id"] as? String, distinctIdBeforeIdentify, "wrong $anon_distinct_id") + } + + func testIdentifyResetTrack() { + stubTrack() + stubEngage() + + let originalDistinctId: String? = mixpanel.distinctId + let distinctId = "testIdentifyTrack" + mixpanel.reset() + waitForTrackingQueue() + + for i in 1...3 { + let prevDistinctId: String? = mixpanel.distinctId + let newDistinctId = distinctId + String(i) + mixpanel.identify(distinctId: newDistinctId) + waitForTrackingQueue() + waitForTrackingQueue() + + let e: InternalProperties = mixpanel.eventsQueue.last! + XCTAssertEqual(e["event"] as? String, "$identify", "incorrect event name") + let p: InternalProperties = e["properties"] as! InternalProperties + XCTAssertEqual(p["distinct_id"] as? String, newDistinctId, "wrong distinct_id") + XCTAssertEqual(p["$anon_distinct_id"] as? String, prevDistinctId, "wrong $anon_distinct_id") + XCTAssertNotEqual(prevDistinctId, originalDistinctId, "After reset, UUID will be used - never the same"); + #if MIXPANEL_UNIQUE_DISTINCT_ID + XCTAssertEqual(prevDistinctId, originalDistinctId, "After reset, IFV will be used - always the same"); + #endif + mixpanel.reset() + waitForTrackingQueue() + } + } + + func testPersistentIdentity() { + stubTrack() + let anonymousId: String? = mixpanel.anonymousId + let distinctId: String = "d1" + let alias: String = "a1" + mixpanel.identify(distinctId: distinctId) + waitForTrackingQueue() + mixpanel.createAlias(alias, distinctId: mixpanel.distinctId) + waitForTrackingQueue() + var tuple = Persistence.restoreIdentity(token: mixpanel.apiToken) + XCTAssertTrue(distinctId == tuple.0 && distinctId == tuple.1 && anonymousId == tuple.2 && distinctId == tuple.3 && alias == tuple.4) + mixpanel.archive() + waitForTrackingQueue() + mixpanel.unarchive() + waitForTrackingQueue() + tuple = Persistence.restoreIdentity(token: mixpanel.apiToken) + XCTAssertTrue(mixpanel.distinctId == tuple.0 && mixpanel.people.distinctId == tuple.1 && mixpanel.anonymousId == tuple.2 && + mixpanel.userId == tuple.3 && mixpanel.alias == tuple.4) + Persistence.deleteMPUserDefaultsData(token: mixpanel.apiToken) + waitForTrackingQueue() + tuple = Persistence.restoreIdentity(token: mixpanel.apiToken) + XCTAssertTrue("" == tuple.0 && nil == tuple.1 && nil == tuple.2 && nil == tuple.3 && nil == tuple.4) + } + + func testHadPersistedDistinctId() { + stubTrack() + XCTAssertNotNil(mixpanel.anonymousId) + XCTAssertNotNil(mixpanel.distinctId) + let distinctId: String = "d1" + mixpanel.anonymousId = nil + mixpanel.userId = nil + mixpanel.alias = nil + mixpanel.distinctId = distinctId + mixpanel.archive() + + XCTAssertEqual(mixpanel.distinctId, distinctId) + + let userId: String = "u1" + mixpanel.identify(distinctId: userId) + waitForTrackingQueue() + XCTAssertEqual(mixpanel.anonymousId, distinctId) + XCTAssertEqual(mixpanel.userId, userId) + XCTAssertEqual(mixpanel.distinctId, userId) + XCTAssertTrue(mixpanel.hadPersistedDistinctId!) + } + + func testTrackWithDefaultProperties() { + mixpanel.track(event: "Something Happened") + waitForTrackingQueue() + let e: InternalProperties = mixpanel.eventsQueue.last! + XCTAssertEqual(e["event"] as? String, "Something Happened", "incorrect event name") + let p: InternalProperties = e["properties"] as! InternalProperties + XCTAssertNotNil(p["$app_build_number"], "$app_build_number not set") + XCTAssertNotNil(p["$app_version_string"], "$app_version_string not set") + XCTAssertNotNil(p["$lib_version"], "$lib_version not set") + XCTAssertNotNil(p["$model"], "$model not set") + XCTAssertNotNil(p["$os"], "$os not set") + XCTAssertNotNil(p["$os_version"], "$os_version not set") + XCTAssertNotNil(p["$screen_height"], "$screen_height not set") + XCTAssertNotNil(p["$screen_width"], "$screen_width not set") + XCTAssertNotNil(p["distinct_id"], "distinct_id not set") + XCTAssertNotNil(p["time"], "time not set") + XCTAssertEqual(p["$manufacturer"] as? String, "Apple", "incorrect $manufacturer") + XCTAssertEqual(p["mp_lib"] as? String, "swift", "incorrect mp_lib") + XCTAssertEqual(p["token"] as? String, kTestToken, "incorrect token") + } + + func testTrackWithCustomProperties() { + let now = Date() + let p: Properties = ["string": "yello", + "number": 3, + "date": now, + "$app_version": "override"] + mixpanel.track(event: "Something Happened", properties: p) + waitForTrackingQueue() + let props: InternalProperties = mixpanel.eventsQueue.last?["properties"] as! InternalProperties + XCTAssertEqual(props["string"] as? String, "yello") + XCTAssertEqual(props["number"] as? Int, 3) + XCTAssertEqual(props["date"] as? Date, now) + XCTAssertEqual(props["$app_version"] as? String, "override", + "reserved property override failed") + } + + func testTrackWithOptionalProperties() { + let optNil: Double? = nil + let optDouble: Double? = 1.0 + let optArray: Array = [nil, 1.0, 2.0] + let optDict: Dictionary = ["nil": nil, "double": 1.0] + let nested: Dictionary = ["list": optArray, "dict": optDict] + let p: Properties = ["nil": optNil, + "double": optDouble, + "list": optArray, + "dict": optDict, + "nested": nested, + ] + mixpanel.track(event: "Optional Test", properties: p) + waitForTrackingQueue() + let props: InternalProperties = mixpanel.eventsQueue.last?["properties"] as! InternalProperties + XCTAssertNil(props["nil"] as? Double) + XCTAssertEqual(props["double"] as? Double, 1.0) + XCTAssertEqual(props["list"] as? Array, [nil, 1.0, 2.0]) + XCTAssertEqual(props["dict"] as? Dictionary, ["nil": nil, "double": 1.0]) + let nestedProp = props["nested"] as? Dictionary + XCTAssertEqual(nestedProp?["dict"] as? Dictionary, ["nil": nil, "double": 1.0]) + XCTAssertEqual(nestedProp?["list"] as? Array, [nil, 1.0, 2.0]) + } + + func testTrackWithCustomDistinctIdAndToken() { + let p: Properties = ["token": "t1", "distinct_id": "d1"] + mixpanel.track(event: "e1", properties: p) + waitForTrackingQueue() + let trackToken = (mixpanel.eventsQueue.last?["properties"] as? InternalProperties)?["token"] as? String + let trackDistinctId = (mixpanel.eventsQueue.last?["properties"] as? InternalProperties)?["distinct_id"] as? String + XCTAssertEqual(trackToken, "t1", "user-defined distinct id not used in track.") + XCTAssertEqual(trackDistinctId, "d1", "user-defined distinct id not used in track.") + } + + func testTrackWithGroups() { + let groupKey = "test_key" + let groupID = "test_id" + mixpanel.trackWithGroups(event: "Something Happened", properties: [groupKey: "some other value", "p1": "value"], groups: [groupKey: groupID]) + waitForTrackingQueue() + let e: InternalProperties = mixpanel.eventsQueue.last! + XCTAssertEqual(e["event"] as? String, "Something Happened", "incorrect event name") + let p: InternalProperties = e["properties"] as! InternalProperties + XCTAssertNotNil(p["$app_build_number"], "$app_build_number not set") + XCTAssertNotNil(p["$app_version_string"], "$app_version_string not set") + XCTAssertNotNil(p["$lib_version"], "$lib_version not set") + XCTAssertNotNil(p["$model"], "$model not set") + XCTAssertNotNil(p["$os"], "$os not set") + XCTAssertNotNil(p["$os_version"], "$os_version not set") + XCTAssertNotNil(p["$screen_height"], "$screen_height not set") + XCTAssertNotNil(p["$screen_width"], "$screen_width not set") + XCTAssertNotNil(p["distinct_id"], "distinct_id not set") + XCTAssertNotNil(p["time"], "time not set") + XCTAssertEqual(p["$manufacturer"] as? String, "Apple", "incorrect $manufacturer") + XCTAssertEqual(p["mp_lib"] as? String, "swift", "incorrect mp_lib") + XCTAssertEqual(p["token"] as? String, kTestToken, "incorrect token") + XCTAssertEqual(p[groupKey] as? String, groupID, "incorrect group id") + XCTAssertEqual(p["p1"] as? String, "value", "incorrect group value") + } + + func testRegisterSuperProperties() { + var p: Properties = ["p1": "a", "p2": 3, "p3": Date()] + mixpanel.registerSuperProperties(p) + waitForTrackingQueue() + XCTAssertEqual(NSDictionary(dictionary: mixpanel.currentSuperProperties()), + NSDictionary(dictionary: p), + "register super properties failed") + p = ["p1": "b"] + mixpanel.registerSuperProperties(p) + waitForTrackingQueue() + XCTAssertEqual(mixpanel.currentSuperProperties()["p1"] as? String, "b", + "register super properties failed to overwrite existing value") + p = ["p4": "a"] + mixpanel.registerSuperPropertiesOnce(p) + waitForTrackingQueue() + XCTAssertEqual(mixpanel.currentSuperProperties()["p4"] as? String, "a", + "register super properties once failed first time") + p = ["p4": "b"] + mixpanel.registerSuperPropertiesOnce(p) + waitForTrackingQueue() + XCTAssertEqual(mixpanel.currentSuperProperties()["p4"] as? String, "a", + "register super properties once failed second time") + p = ["p4": "c"] + mixpanel.registerSuperPropertiesOnce(p, defaultValue: "d") + waitForTrackingQueue() + XCTAssertEqual(mixpanel.currentSuperProperties()["p4"] as? String, "a", + "register super properties once with default value failed when no match") + mixpanel.registerSuperPropertiesOnce(p, defaultValue: "a") + waitForTrackingQueue() + XCTAssertEqual(mixpanel.currentSuperProperties()["p4"] as? String, "c", + "register super properties once with default value failed when match") + mixpanel.unregisterSuperProperty("a") + waitForTrackingQueue() + XCTAssertNil(mixpanel.currentSuperProperties()["a"], + "unregister super property failed") + // unregister non-existent super property should not throw + mixpanel.unregisterSuperProperty("a") + mixpanel.clearSuperProperties() + waitForTrackingQueue() + XCTAssertTrue(mixpanel.currentSuperProperties().isEmpty, + "clear super properties failed") + } + + func testInvalidPropertiesTrack() { + let p: Properties = ["data": [Data()]] + XCTExpectAssert("property type should not be allowed") { + mixpanel.track(event: "e1", properties: p) + } + } + + func testInvalidSuperProperties() { + let p: Properties = ["data": [Data()]] + XCTExpectAssert("property type should not be allowed") { + mixpanel.registerSuperProperties(p) + } + } + + func testInvalidSuperProperties2() { + let p: Properties = ["data": [Data()]] + XCTExpectAssert("property type should not be allowed") { + mixpanel.registerSuperPropertiesOnce(p) + } + } + + func testInvalidSuperProperties3() { + let p: Properties = ["data": [Data()]] + XCTExpectAssert("property type should not be allowed") { + mixpanel.registerSuperPropertiesOnce(p, defaultValue: "v") + } + } + + func testValidPropertiesTrack() { + let p: Properties = allPropertyTypes() + mixpanel.track(event: "e1", properties: p) + } + + func testValidSuperProperties() { + let p: Properties = allPropertyTypes() + mixpanel.registerSuperProperties(p) + mixpanel.registerSuperPropertiesOnce(p) + mixpanel.registerSuperPropertiesOnce(p, defaultValue: "v") + } + + + func testReset() { + stubTrack() + stubEngage() + mixpanel.identify(distinctId: "d1") + mixpanel.track(event: "e1") + let p: Properties = ["p1": "a"] + mixpanel.registerSuperProperties(p) + mixpanel.people.set(properties: p) + mixpanel.archive() + mixpanel.reset() + waitForTrackingQueue() + #if MIXPANEL_UNIQUE_DISTINCT_ID + XCTAssertEqual(mixpanel.distinctId, + mixpanel.defaultDistinctId(), + "distinct id failed to reset") + #endif + XCTAssertNil(mixpanel.people.distinctId, "people distinct id failed to reset") + XCTAssertTrue(mixpanel.currentSuperProperties().isEmpty, + "super properties failed to reset") + XCTAssertTrue(mixpanel.eventsQueue.isEmpty, "events queue failed to reset") + XCTAssertTrue(mixpanel.people.peopleQueue.isEmpty, "people queue failed to reset") + mixpanel = Mixpanel.initialize(token: kTestToken) + waitForTrackingQueue() + #if MIXPANEL_UNIQUE_DISTINCT_ID + XCTAssertEqual(mixpanel.distinctId, mixpanel.defaultDistinctId(), + "distinct id failed to reset after archive") + #endif + XCTAssertNil(mixpanel.people.distinctId, + "people distinct id failed to reset after archive") + XCTAssertTrue(mixpanel.currentSuperProperties().isEmpty, + "super properties failed to reset after archive") + XCTAssertTrue(mixpanel.eventsQueue.isEmpty, + "events queue failed to reset after archive") + XCTAssertTrue(mixpanel.people.peopleQueue.isEmpty, + "people queue failed to reset after archive") + } + + func testArchiveNSNumberBoolIntProperty() { + let testToken = randomId() + mixpanel = Mixpanel.initialize(token: testToken) + let aBoolNumber: Bool = true + let aBoolNSNumber = NSNumber(value: aBoolNumber) + + let aIntNumber: Int = 1 + let aIntNSNumber = NSNumber(value: aIntNumber) + + mixpanel.track(event: "e1", properties: ["p1": aBoolNSNumber, "p2": aIntNSNumber]) + mixpanel.archive() + waitForTrackingQueue() + mixpanel = Mixpanel.initialize(token: testToken) + waitForTrackingQueue() + let properties: [String: Any] = mixpanel.eventsQueue[0]["properties"] as! [String: Any] + + XCTAssertTrue(isBoolNumber(num: properties["p1"]! as! NSNumber), + "The bool value should be unarchived as bool") + XCTAssertFalse(isBoolNumber(num: properties["p2"]! as! NSNumber), + "The int value should not be unarchived as bool") + } + + private func isBoolNumber(num: NSNumber) -> Bool + { + let boolID = CFBooleanGetTypeID() // the type ID of CFBoolean + let numID = CFGetTypeID(num) // the type ID of num + return numID == boolID + } + + func testArchive() { + let testToken = randomId() + mixpanel = Mixpanel.initialize(token: testToken) + #if MIXPANEL_UNIQUE_DISTINCT_ID + XCTAssertEqual(mixpanel.distinctId, mixpanel.defaultDistinctId(), + "default distinct id archive failed") + #endif + XCTAssertTrue(mixpanel.currentSuperProperties().isEmpty, + "default super properties archive failed") + XCTAssertTrue(mixpanel.eventsQueue.isEmpty, "default events queue archive failed") + XCTAssertNil(mixpanel.people.distinctId, "default people distinct id archive failed") + XCTAssertTrue(mixpanel.people.peopleQueue.isEmpty, "default people queue archive failed") + let p: Properties = ["p1": "a"] + mixpanel.identify(distinctId: "d1") + mixpanel.registerSuperProperties(p) + sleep(2) + mixpanel.track(event: "e1") + mixpanel.track(event: "e3") + mixpanel.track(event: "e4") + mixpanel.track(event: "e5") + mixpanel.track(event: "e6") + mixpanel.track(event: "e7") + mixpanel.track(event: "e8") + mixpanel.track(event: "e9") + mixpanel.track(event: "e10") + mixpanel.people.set(properties: p) + mixpanel.timedEvents["e2"] = 5 + mixpanel.archive() + waitForTrackingQueue() + + mixpanel = Mixpanel.initialize(token: testToken) + waitForTrackingQueue() + XCTAssertEqual(mixpanel.distinctId, "d1", "custom distinct archive failed") + XCTAssertTrue(mixpanel.currentSuperProperties().count == 1, + "custom super properties archive failed") + XCTAssertEqual(mixpanel.eventsQueue[1]["event"] as? String, "e1", + "event was not successfully archived/unarchived") + XCTAssertEqual(mixpanel.eventsQueue[2]["event"] as? String, "e3", + "event was not successfully archived/unarchived or order is incorrect") + XCTAssertEqual(mixpanel.eventsQueue[3]["event"] as? String, "e4", + "event was not successfully archived/unarchived or order is incorrect") + XCTAssertEqual(mixpanel.eventsQueue[4]["event"] as? String, "e5", + "event was not successfully archived/unarchived or order is incorrect") + XCTAssertEqual(mixpanel.eventsQueue[5]["event"] as? String, "e6", + "event was not successfully archived/unarchived or order is incorrect") + XCTAssertEqual(mixpanel.eventsQueue[6]["event"] as? String, "e7", + "event was not successfully archived/unarchived or order is incorrect") + XCTAssertEqual(mixpanel.eventsQueue[7]["event"] as? String, "e8", + "event was not successfully archived/unarchived or order is incorrect") + XCTAssertEqual(mixpanel.eventsQueue[8]["event"] as? String, "e9", + "event was not successfully archived/unarchived or order is incorrect") + XCTAssertEqual(mixpanel.eventsQueue[9]["event"] as? String, "e10", + "event was not successfully archived/unarchived or order is incorrect") + XCTAssertEqual(mixpanel.people.distinctId, "d1", + "custom people distinct id archive failed") + XCTAssertTrue(mixpanel.people.peopleQueue.count == 1, "pending people queue archive failed") + XCTAssertEqual(mixpanel.timedEvents["e2"] as? Double, 5.0, + "timedEvents archive failed") + let fileManager = FileManager.default + XCTAssertTrue(fileManager.fileExists( + atPath: Persistence.filePathWithType(.events, token: testToken)!), + "events archive file not removed") + XCTAssertTrue(fileManager.fileExists( + atPath: Persistence.filePathWithType(.people, token: testToken)!), + "people archive file not removed") + XCTAssertTrue(fileManager.fileExists( + atPath: Persistence.filePathWithType(.properties, token: testToken)!), + "properties archive file not removed") + mixpanel = Mixpanel.initialize(token: testToken) + XCTAssertEqual(mixpanel.distinctId, "d1", "expecting d1 as distinct id as initialised") + XCTAssertTrue(mixpanel.currentSuperProperties().count == 1, + "default super properties expected to have 1 item") + XCTAssertNotNil(mixpanel.eventsQueue, "default events queue from no file is nil") + XCTAssertTrue(mixpanel.eventsQueue.count == 10, "default events queue expecting 10 items ($identify call added)") + XCTAssertNotNil(mixpanel.people.distinctId, + "default people distinct id from no file failed") + XCTAssertNotNil(mixpanel.people.peopleQueue, "default people queue from no file is nil") + XCTAssertTrue(mixpanel.people.peopleQueue.count == 1, "default people queue expecting 1 item") + XCTAssertTrue(mixpanel.timedEvents.count == 1, "timedEvents expecting 1 item") + // corrupt file + let garbage = "garbage".data(using: String.Encoding.utf8)! + do { + try garbage.write(to: URL( + fileURLWithPath: Persistence.filePathWithType(.events, token: testToken)!), + options: []) + try garbage.write(to: URL( + fileURLWithPath: Persistence.filePathWithType(.people, token: testToken)!), + options: []) + try garbage.write(to: URL( + fileURLWithPath: Persistence.filePathWithType(.properties, token: testToken)!), + options: []) + } catch { + print("couldn't write data") + } + XCTAssertTrue(fileManager.fileExists( + atPath: Persistence.filePathWithType(.events, token: testToken)!), + "events archive file not removed") + XCTAssertTrue(fileManager.fileExists( + atPath: Persistence.filePathWithType(.people, token: testToken)!), + "people archive file not removed") + XCTAssertTrue(fileManager.fileExists( + atPath: Persistence.filePathWithType(.properties, token: testToken)!), + "properties archive file not removed") + Persistence.deleteMPUserDefaultsData(token: testToken) + mixpanel = Mixpanel.initialize(token: testToken) + waitForTrackingQueue() + #if MIXPANEL_UNIQUE_DISTINCT_ID + XCTAssertEqual(mixpanel.distinctId, mixpanel.defaultDistinctId(), + "default distinct id from garbage failed") + #endif + XCTAssertTrue(mixpanel.currentSuperProperties().isEmpty, + "default super properties from garbage failed") + XCTAssertNotNil(mixpanel.eventsQueue, "default events queue from garbage is nil") + XCTAssertTrue(mixpanel.eventsQueue.isEmpty, + "default events queue from garbage not empty") + XCTAssertNil(mixpanel.people.distinctId, + "default people distinct id from garbage failed") + XCTAssertNotNil(mixpanel.people.peopleQueue, + "default people queue from garbage is nil") + XCTAssertTrue(mixpanel.people.peopleQueue.isEmpty, + "default people queue from garbage not empty") + XCTAssertTrue(mixpanel.timedEvents.isEmpty, + "timedEvents is not empty") + } + + + func testUnarchiveCorruptedData() { + // corrupt file + let fileManager = FileManager.default + let garbage = "garbage".data(using: String.Encoding.utf8)! + let testToken = randomId() + + do { + try garbage.write(to: URL( + fileURLWithPath: Persistence.filePathWithType(.events, token: testToken)!), + options: []) + try garbage.write(to: URL( + fileURLWithPath: Persistence.filePathWithType(.people, token: testToken)!), + options: []) + try garbage.write(to: URL( + fileURLWithPath: Persistence.filePathWithType(.properties, token: testToken)!), + options: []) + try garbage.write(to: URL( + fileURLWithPath: Persistence.filePathWithType(.codelessBindings, token: testToken)!), + options: []) + try garbage.write(to: URL( + fileURLWithPath: Persistence.filePathWithType(.variants, token: testToken)!), + options: []) + try garbage.write(to: URL( + fileURLWithPath: Persistence.filePathWithType(.optOutStatus, token: testToken)!), + options: []) + } catch { + print("couldn't write data") + } + + mixpanel = Mixpanel.initialize(token: testToken) + waitForTrackingQueue() + + + XCTAssertTrue(!fileManager.fileExists( + atPath: Persistence.filePathWithType(.events, token: testToken)!), + "events archive file not removed") + XCTAssertTrue(!fileManager.fileExists( + atPath: Persistence.filePathWithType(.people, token: testToken)!), + "people archive file not removed") + XCTAssertTrue(!fileManager.fileExists( + atPath: Persistence.filePathWithType(.properties, token: testToken)!), + "properties archive file not removed") + XCTAssertTrue(!fileManager.fileExists( + atPath: Persistence.filePathWithType(.optOutStatus, token: testToken)!), + "properties archive file not removed") + waitForTrackingQueue() + } + + func testMixpanelDelegate() { + mixpanel.delegate = self + mixpanel.identify(distinctId: "d1") + mixpanel.track(event: "e1") + mixpanel.people.set(property: "p1", to: "a") + waitForTrackingQueue() + flushAndWaitForNetworkQueue() + XCTAssertTrue(mixpanel.eventsQueue.count == 2, "delegate should have stopped flush") + XCTAssertTrue(mixpanel.people.peopleQueue.count == 1, "delegate should have stopped flush") + } + + func testEventTiming() { + mixpanel.track(event: "Something Happened") + waitForTrackingQueue() + var e: InternalProperties = mixpanel.eventsQueue.last! + var p = e["properties"] as! InternalProperties + XCTAssertNil(p["$duration"], "New events should not be timed.") + mixpanel.time(event: "400 Meters") + mixpanel.track(event: "500 Meters") + waitForTrackingQueue() + e = mixpanel.eventsQueue.last! + p = e["properties"] as! InternalProperties + XCTAssertNil(p["$duration"], "The exact same event name is required for timing.") + mixpanel.track(event: "400 Meters") + waitForTrackingQueue() + e = mixpanel.eventsQueue.last! + p = e["properties"] as! InternalProperties + XCTAssertNotNil(p["$duration"], "This event should be timed.") + mixpanel.track(event: "400 Meters") + waitForTrackingQueue() + e = mixpanel.eventsQueue.last! + p = e["properties"] as! InternalProperties + XCTAssertNil(p["$duration"], + "Tracking the same event should require a second call to timeEvent.") + mixpanel.time(event: "Time Event A") + mixpanel.time(event: "Time Event B") + mixpanel.time(event: "Time Event C") + waitForTrackingQueue() + XCTAssertTrue(mixpanel.timedEvents.count == 3, "Each call to time() should add an event to timedEvents") + XCTAssertNotNil(mixpanel.timedEvents["Time Event A"], "Keys in timedEvents should be event names") + mixpanel.clearTimedEvent(event: "Time Event A") + waitForTrackingQueue() + XCTAssertNil(mixpanel.timedEvents["Time Event A"], "clearTimedEvent should remove key/value pair") + XCTAssertTrue(mixpanel.timedEvents.count == 2, "clearTimedEvent shoud remove only one key/value pair") + mixpanel.clearTimedEvents() + waitForTrackingQueue() + XCTAssertTrue(mixpanel.timedEvents.count == 0, "clearTimedEvents should remove all key/value pairs") + } + + func testReadWriteLock() { + var array = [Int]() + let lock = ReadWriteLock(label: "test") + let queue = DispatchQueue(label: "concurrent", qos: .utility, attributes: .concurrent) + for _ in 0..<10 { + queue.async { + lock.write { + for i in 0..<100 { + array.append(i) + } + } + } + + queue.async { + lock.read { + XCTAssertTrue(array.count % 100 == 0, "supposed to happen after write") + } + } + } + } + + func testSetGroup() { + stubTrack() + stubEngage() + mixpanel.identify(distinctId: "d1") + let groupKey = "test_key" + let groupValue = "test_value" + mixpanel.setGroup(groupKey: groupKey, groupIDs: [groupValue]) + waitForTrackingQueue() + XCTAssertEqual(mixpanel.currentSuperProperties()[groupKey] as? [String], [groupValue]) + let q = mixpanel.people.peopleQueue.last!["$set"] as! InternalProperties + XCTAssertEqual(q[groupKey] as? [String], [groupValue], "group value people property not queued") + assertDefaultPeopleProperties(q) + } + + func testAddGroup() { + stubTrack() + stubEngage() + mixpanel.identify(distinctId: "d1") + let groupKey = "test_key" + let groupValue = "test_value" + + mixpanel.addGroup(groupKey: groupKey, groupID: groupValue) + waitForMixpanelQueues() + XCTAssertEqual(mixpanel.currentSuperProperties()[groupKey] as? [String], [groupValue]) + waitForMixpanelQueues() + let q = mixpanel.people.peopleQueue.last!["$set"] as! InternalProperties + XCTAssertEqual(q[groupKey] as? [String], [groupValue], "addGroup people update not queued") + assertDefaultPeopleProperties(q) + + mixpanel.addGroup(groupKey: groupKey, groupID: groupValue) + waitForMixpanelQueues() + XCTAssertEqual(mixpanel.currentSuperProperties()[groupKey] as? [String], [groupValue]) + waitForMixpanelQueues() + let q2 = mixpanel.people.peopleQueue.last!["$union"] as! InternalProperties + XCTAssertEqual(q2[groupKey] as? [String], [groupValue], "addGroup people update not queued") + + let newVal = "new_group" + mixpanel.addGroup(groupKey: groupKey, groupID: newVal) + waitForMixpanelQueues() + XCTAssertEqual(mixpanel.currentSuperProperties()[groupKey] as? [String], [groupValue, newVal]) + waitForMixpanelQueues() + let q3 = mixpanel.people.peopleQueue.last!["$union"] as! InternalProperties + XCTAssertEqual(q3[groupKey] as? [String], [newVal], "addGroup people update not queued") + } + + func testRemoveGroup() { + stubTrack() + stubEngage() + mixpanel.identify(distinctId: "d1") + let groupKey = "test_key" + let groupValue = "test_value" + let newVal = "new_group" + + mixpanel.setGroup(groupKey: groupKey, groupIDs: [groupValue, newVal]) + waitForTrackingQueue() + XCTAssertEqual(mixpanel.currentSuperProperties()[groupKey] as? [String], [groupValue, newVal]) + + mixpanel.removeGroup(groupKey: groupKey, groupID: groupValue) + waitForTrackingQueue() + XCTAssertEqual(mixpanel.currentSuperProperties()[groupKey] as? [String], [newVal]) + waitForTrackingQueue() + let q2 = mixpanel.people.peopleQueue.last!["$remove"] as! InternalProperties + XCTAssertEqual(q2[groupKey] as? String, groupValue, "removeGroup people update not queued") + + mixpanel.removeGroup(groupKey: groupKey, groupID: groupValue) + waitForTrackingQueue() + XCTAssertNil(mixpanel.currentSuperProperties()[groupKey]) + waitForTrackingQueue() + let q3 = mixpanel.people.peopleQueue.last!["$unset"] as! [String] + XCTAssertEqual(q3, [groupKey], "removeGroup people update not queued") + } +} diff --git a/MixpanelDemo/MixpanelDemoMacTests/MixpanelGroupTests.swift b/MixpanelDemo/MixpanelDemoMacTests/MixpanelGroupTests.swift new file mode 100644 index 000000000..24e9826a8 --- /dev/null +++ b/MixpanelDemo/MixpanelDemoMacTests/MixpanelGroupTests.swift @@ -0,0 +1,141 @@ +// +// MixpanelGroupTests.swift +// MixpanelDemo +// +// Created by Iris McLeary on 9/5/2018. +// Copyright © 2018 Mixpanel. All rights reserved. +// + +import XCTest +import Nocilla + +@testable import Mixpanel +@testable import MixpanelDemoMac + +class MixpanelGroupTests: MixpanelBaseTests { + + func testGroupSet() { + let groupKey = "test_key" + let groupID = "test_id" + let p: Properties = ["p1": "a"] + mixpanel.getGroup(groupKey: groupKey, groupID: groupID).set(properties: p) + waitForTrackingQueue() + let msg = mixpanel.groupsQueue.last! + XCTAssertEqual(msg["$group_key"] as! String, groupKey) + XCTAssertEqual(msg["$group_id"] as! String, groupID) + let q = msg["$set"] as! InternalProperties + XCTAssertEqual(q["p1"] as? String, "a", "custom group property not queued") + } + + func testGroupSetIntegerID() { + let groupKey = "test_key" + let groupID = 3 + let p: Properties = ["p1": "a"] + mixpanel.getGroup(groupKey: groupKey, groupID: groupID).set(properties: p) + waitForTrackingQueue() + let msg = mixpanel.groupsQueue.last! + XCTAssertEqual(msg["$group_key"] as! String, groupKey) + XCTAssertEqual(msg["$group_id"] as! Int, groupID) + let q = msg["$set"] as! InternalProperties + XCTAssertEqual(q["p1"] as? String, "a", "custom group property not queued") + } + + func testGroupSetOnce() { + let groupKey = "test_key" + let groupID = "test_id" + let p: Properties = ["p1": "a"] + mixpanel.getGroup(groupKey: groupKey, groupID: groupID).setOnce(properties: p) + waitForTrackingQueue() + let msg = mixpanel.groupsQueue.last! + XCTAssertEqual(msg["$group_key"] as! String, groupKey) + XCTAssertEqual(msg["$group_id"] as! String, groupID) + let q = msg["$set_once"] as! InternalProperties + XCTAssertEqual(q["p1"] as? String, "a", "custom group property not queued") + } + + func testGroupSetTo() { + let groupKey = "test_key" + let groupID = "test_id" + mixpanel.getGroup(groupKey: groupKey, groupID: groupID).set(property: "p1", to: "a") + waitForTrackingQueue() + let msg = mixpanel.groupsQueue.last! + XCTAssertEqual(msg["$group_key"] as! String, groupKey) + XCTAssertEqual(msg["$group_id"] as! String, groupID) + let p = msg["$set"] as! InternalProperties + XCTAssertEqual(p["p1"] as? String, "a", "custom group property not queued") + } + + func testGroupUnset() { + let groupKey = "test_key" + let groupID = "test_id" + mixpanel.getGroup(groupKey: groupKey, groupID: groupID).unset(property: "p1") + waitForTrackingQueue() + let msg = mixpanel.groupsQueue.last! + XCTAssertEqual(msg["$group_key"] as! String, groupKey) + XCTAssertEqual(msg["$group_id"] as! String, groupID) + XCTAssertEqual(msg["$unset"] as! [String], ["p1"], "group property unset not queued") + } + + func testGroupRemove() { + let groupKey = "test_key" + let groupID = "test_id" + mixpanel.getGroup(groupKey: groupKey, groupID: groupID).remove(key: "p1", value: "a") + waitForTrackingQueue() + let msg = mixpanel.groupsQueue.last! + XCTAssertEqual(msg["$group_key"] as! String, groupKey) + XCTAssertEqual(msg["$group_id"] as! String, groupID) + XCTAssertEqual(msg["$remove"] as? [String: String], ["p1": "a"], "group property remove not queued") + } + + func testGroupUnion() { + let groupKey = "test_key" + let groupID = "test_id" + mixpanel.getGroup(groupKey: groupKey, groupID: groupID).union(key: "p1", values: ["a"]) + waitForTrackingQueue() + let msg = mixpanel.groupsQueue.last! + XCTAssertEqual(msg["$group_key"] as! String, groupKey) + XCTAssertEqual(msg["$group_id"] as! String, groupID) + XCTAssertEqual(msg["$union"] as? [String: [String]], ["p1": ["a"]], "group property union not queued") + } + + func testDropGroupRecords() { + QueueConstants.queueSize = 500 + let groupKey = "test_key" + let groupID = "test_id" + for i in 0..<505 { + mixpanel.getGroup(groupKey: groupKey, groupID: groupID).set(property: "i", to: i) + } + waitForTrackingQueue() + XCTAssertTrue(mixpanel.groupsQueue.count == 500) + var r: InternalProperties = mixpanel.groupsQueue.first! + XCTAssertEqual(r["$group_key"] as! String, groupKey) + XCTAssertEqual(r["$group_id"] as! String, groupID) + XCTAssertEqual((r["$set"] as? InternalProperties)?["i"] as? Int, 5) + r = mixpanel.groupsQueue.last! + XCTAssertEqual((r["$set"] as? InternalProperties)?["i"] as? Int, 504) + } + + func testGroupAssertPropertyTypes() { + let groupKey = "test_key" + let groupID = "test_id" + let p: Properties = ["URL": [Data()]] + XCTExpectAssert("unsupported property type was allowed") { + mixpanel.getGroup(groupKey: groupKey, groupID: groupID).set(properties: p) + } + XCTExpectAssert("unsupported property type was allowed") { + mixpanel.getGroup(groupKey: groupKey, groupID: groupID).set(property: "p1", to: [Data()]) + } + } + + func testDeleteGroup() { + let groupKey = "test_key" + let groupID = "test_id" + mixpanel.getGroup(groupKey: groupKey, groupID: groupID).deleteGroup() + waitForTrackingQueue() + let msg = mixpanel.groupsQueue.last! + XCTAssertEqual(msg["$group_key"] as! String, groupKey) + XCTAssertEqual(msg["$group_id"] as! String, groupID) + let p: InternalProperties = msg["$delete"] as! InternalProperties + XCTAssertTrue(p.isEmpty, "incorrect group properties: \(p)") + } +} diff --git a/MixpanelDemo/MixpanelDemoMacTests/MixpanelOptOutTests.swift b/MixpanelDemo/MixpanelDemoMacTests/MixpanelOptOutTests.swift new file mode 100644 index 000000000..d04d37f02 --- /dev/null +++ b/MixpanelDemo/MixpanelDemoMacTests/MixpanelOptOutTests.swift @@ -0,0 +1,260 @@ +// +// MixpanelOptOutTests.swift +// MixpanelDemoTests +// +// Created by Zihe Jia on 3/27/18. +// Copyright © 2018 Mixpanel. All rights reserved. +// + +import XCTest +@testable import Mixpanel + +class MixpanelOptOutTests: MixpanelBaseTests { + + func testHasOptOutTrackingFlagBeingSetProperlyAfterInitializedWithOptedOutYES() + { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) + XCTAssertTrue(mixpanel.hasOptedOutTracking(), "When initialize with opted out flag set to YES, the current user should have opted out tracking") + } + + func testOptInWillAddOptInEvent() + { + mixpanel.optInTracking() + XCTAssertFalse(mixpanel.hasOptedOutTracking(), "The current user should have opted in tracking") + waitForMixpanelQueues() + XCTAssertTrue(mixpanel.eventsQueue.count == 1, "When opted in, event queue should have one even(opt in) being queued") + + if mixpanel.eventsQueue.count > 0 { + let event = mixpanel.eventsQueue.first + XCTAssertEqual((event!["event"] as? String), "$opt_in", "When opted in, a track '$opt_in' should have been queued") + } + else { + XCTAssertTrue(mixpanel.eventsQueue.count == 1, "When opted in, event queue should have one even(opt in) being queued") + } + } + + func testOptInTrackingForDistinctId() + { + mixpanel .optInTracking(distinctId: "testDistinctId") + XCTAssertFalse(mixpanel.hasOptedOutTracking(), "The current user should have opted in tracking") + waitForTrackingQueue() + if mixpanel.eventsQueue.count > 0 { + let event = mixpanel.eventsQueue.first + XCTAssertEqual((event!["event"] as? String), "$opt_in", "When opted in, a track '$opt_in' should have been queued") + } + else { + XCTAssertTrue(mixpanel.eventsQueue.count == 1, "When opted in, event queue should have one even(opt in) being queued") + } + + XCTAssertEqual(mixpanel.distinctId, "testDistinctId", "mixpanel identify failed to set distinct id") + XCTAssertEqual(mixpanel.people.distinctId, "testDistinctId", "mixpanel identify failed to set people distinct id") + XCTAssertTrue(mixpanel.people.unidentifiedQueue.count == 0, "identify: should move records from unidentified queue") + } + + func testOptInTrackingForDistinctIdAndWithEventProperties() + { + let now = Date() + let testProperties: Properties = ["string": "yello", + "number": 3, + "date": now, + "$app_version": "override"] + mixpanel.optInTracking(distinctId: "testDistinctId", properties: testProperties) + waitForTrackingQueue() + let props = mixpanel.eventsQueue.first!["properties"] as? InternalProperties + XCTAssertEqual(props!["string"] as? String, "yello") + XCTAssertEqual(props!["number"] as? NSNumber, 3) + XCTAssertEqual(props!["date"] as? Date, now) + XCTAssertEqual(props!["$app_version"] as? String, "override", "reserved property override failed") + + if mixpanel.eventsQueue.count > 0 { + let event = mixpanel.eventsQueue.first + XCTAssertEqual((event!["event"] as? String), "$opt_in", "When opted in, a track '$opt_in' should have been queued") + } + else { + XCTAssertTrue(mixpanel.eventsQueue.count == 1, "When opted in, event queue should have one even(opt in) being queued") + } + + XCTAssertEqual(mixpanel.distinctId, "testDistinctId", "mixpanel identify failed to set distinct id") + XCTAssertEqual(mixpanel.people.distinctId, "testDistinctId", "mixpanel identify failed to set people distinct id") + XCTAssertTrue(mixpanel.people.unidentifiedQueue.count == 0, "identify: should move records from unidentified queue") + } + + func testHasOptOutTrackingFlagBeingSetProperlyForMultipleInstances() + { + let mixpanel1 = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) + XCTAssertTrue(mixpanel1.hasOptedOutTracking(), "When initialize with opted out flag set to YES, the current user should have opted out tracking") + + let mixpanel2 = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: false) + XCTAssertFalse(mixpanel2.hasOptedOutTracking(), "When initialize with opted out flag set to NO, the current user should have opted in tracking") + + deleteOptOutSettings(mixpanelInstance: mixpanel1) + deleteOptOutSettings(mixpanelInstance: mixpanel2) + } + + func testHasOptOutTrackingFlagBeingSetProperlyAfterInitializedWithOptedOutNO() + { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: false) + XCTAssertFalse(mixpanel.hasOptedOutTracking(), "When initialize with opted out flag set to NO, the current user should have opted out tracking") + } + + func testHasOptOutTrackingFlagBeingSetProperlyByDefault() + { + mixpanel = Mixpanel.initialize(token: randomId()) + XCTAssertFalse(mixpanel.hasOptedOutTracking(), "By default, the current user should not opted out tracking") + } + + func testHasOptOutTrackingFlagBeingSetProperlyForOptOut() + { + mixpanel.optOutTracking() + XCTAssertTrue(mixpanel.hasOptedOutTracking(), "When optOutTracking is called, the current user should have opted out tracking") + } + + func testHasOptOutTrackingFlagBeingSetProperlyForOptIn() + { + mixpanel.optOutTracking() + XCTAssertTrue(mixpanel.hasOptedOutTracking(), "When optOutTracking is called, the current user should have opted out tracking") + mixpanel.optInTracking() + XCTAssertFalse(mixpanel.hasOptedOutTracking(), "When optOutTracking is called, the current user should have opted in tracking") + } + + func testEventBeingTrackedBeforeOptOutShouldNotBeCleared() + { + mixpanel = Mixpanel.initialize(token: randomId()) + mixpanel.track(event: "a normal event") + waitForMixpanelQueues() + XCTAssertTrue(mixpanel.eventsQueue.count == 1, "events should be queued") + mixpanel.optOutTracking() + waitForMixpanelQueues() + XCTAssertTrue(mixpanel.eventsQueue.count == 1, "When opted out, any events tracked before opted out should not be cleared") + } + + func testOptOutTrackingWillNotGenerateEventQueue() + { + mixpanel.optOutTracking() + for i in 0..<50 { + mixpanel.track(event: "event \(i)") + } + waitForMixpanelQueues() + XCTAssertTrue(mixpanel.eventsQueue.count == 0, "When opted out, events should not be queued") + } + + func testOptOutTrackingWillNotGeneratePeopleQueue() + { + mixpanel.optOutTracking() + for i in 0..<50 { + mixpanel.people.set(property: "p1", to: "\(i)") + } + waitForMixpanelQueues() + XCTAssertTrue(mixpanel.people.peopleQueue.count == 0, "When opted out, events should not be queued") + } + + func testOptOutTrackingWillSkipAlias() + { + mixpanel.optOutTracking() + mixpanel.createAlias("testAlias", distinctId: "aDistinctId") + XCTAssertNotEqual(mixpanel.alias, "testAlias", "When opted out, alias should not be set") + } + + func testOptOutTrackingRegisterSuperProperties() + { + let properties: Properties = ["p1": "a", "p2": 3, "p3": Date()] + mixpanel.optOutTracking() + mixpanel.registerSuperProperties(properties) + waitForMixpanelQueues() + XCTAssertNotEqual(NSDictionary(dictionary: mixpanel.currentSuperProperties()), + NSDictionary(dictionary: properties), + "When opted out, register super properties should not be successful") + } + + func testOptOutTrackingRegisterSuperPropertiesOnce() + { + let properties: Properties = ["p1": "a", "p2": 3, "p3": Date()] + mixpanel.optOutTracking() + mixpanel.registerSuperPropertiesOnce(properties) + waitForMixpanelQueues() + XCTAssertNotEqual(NSDictionary(dictionary: mixpanel.currentSuperProperties()), + NSDictionary(dictionary: properties), + "When opted out, register super properties once should not be successful") + } + + func testOptOutWilSkipTimeEvent() + { + mixpanel.optOutTracking() + mixpanel.time(event: "400 Meters") + mixpanel.track(event: "400 Meters") + waitForMixpanelQueues() + XCTAssertNil(mixpanel.eventsQueue.last, "When opted out, this event should not be timed.") + } + + func testOptOutTrackingWillPurgeEventQueue() + { + mixpanel.optInTracking() + mixpanel.identify(distinctId: "d1") + for i in 0..<50 { + mixpanel.track(event: "event \(i)") + } + waitForMixpanelQueues() + XCTAssertTrue(mixpanel.eventsQueue.count > 50, "When opted in, events should have been queued") + XCTAssertEqual(mixpanel.eventsQueue.first!["event"] as? String, "$opt_in", "incorrect optin event name") + + mixpanel.optOutTracking() + waitForMixpanelQueues() + XCTAssertTrue(mixpanel.eventsQueue.count == 0, "When opted out, events should have been purged") + } + + func testOptOutTrackingWillPurgePeopleQueue() + { + mixpanel.optInTracking() + mixpanel.identify(distinctId: "d1") + for i in 0..<50 { + mixpanel.people.set(property: "p1", to: "\(i)") + } + waitForTrackingQueue() + XCTAssertTrue(mixpanel.people.peopleQueue.count == 50, "When opted in, people should have been queued") + + mixpanel.optOutTracking() + waitForMixpanelQueues() + XCTAssertTrue(mixpanel.people.peopleQueue.count == 0, "When opted out, people should have been purged") + } + + func testOptOutWillSkipFlushPeople() + { + mixpanel.optInTracking() + mixpanel.identify(distinctId: "d1") + for i in 0..<50 { + mixpanel.people.set(property: "p1", to: "\(i)") + } + waitForTrackingQueue() + XCTAssertTrue(mixpanel.people.peopleQueue.count == 50, "When opted in, people queue should have been queued") + + let peopleQueue = mixpanel.people.peopleQueue + mixpanel.optOutTracking() + waitForMixpanelQueues() + + mixpanel.people.peopleQueue = peopleQueue + mixpanel.flush() + waitForMixpanelQueues() + XCTAssertTrue(mixpanel.people.peopleQueue.count == 50, "When opted out, people queue should not be flushed") + } + + func testOptOutWillSkipFlushEvent() + { + mixpanel.optInTracking() + mixpanel.identify(distinctId: "d1") + for i in 0..<50 { + mixpanel.track(event: "event \(i)") + } + waitForTrackingQueue() + XCTAssertTrue(mixpanel.eventsQueue.count > 50, "When opted in, events should have been queued") + + let eventsQueue = mixpanel.eventsQueue + mixpanel.optOutTracking() + waitForMixpanelQueues() + + //In order to test if flush will be skipped, we have to create a fake eventsQueue since optOutTracking will clear eventsQueue. + mixpanel.eventsQueue = eventsQueue + mixpanel.flush() + waitForMixpanelQueues() + XCTAssertTrue(mixpanel.eventsQueue.count > 50, "When opted out, events should not be flushed") + } +} diff --git a/MixpanelDemo/MixpanelDemoMacTests/MixpanelPeopleTests.swift b/MixpanelDemo/MixpanelDemoMacTests/MixpanelPeopleTests.swift new file mode 100644 index 000000000..21c6bc365 --- /dev/null +++ b/MixpanelDemo/MixpanelDemoMacTests/MixpanelPeopleTests.swift @@ -0,0 +1,206 @@ +// +// MixpanelPeopleTests.swift +// MixpanelDemo +// +// Created by Yarden Eitan on 6/28/16. +// Copyright © 2016 Mixpanel. All rights reserved. +// + +import XCTest +import Nocilla + +@testable import Mixpanel +@testable import MixpanelDemoMac + +class MixpanelPeopleTests: MixpanelBaseTests { + + func testPeopleSet() { + mixpanel.identify(distinctId: "d1") + let p: Properties = ["p1": "a"] + mixpanel.people.set(properties: p) + waitForTrackingQueue() + let q = mixpanel.people.peopleQueue.last!["$set"] as! InternalProperties + XCTAssertEqual(q["p1"] as? String, "a", "custom people property not queued") + assertDefaultPeopleProperties(q) + } + + func testPeopleSetOnce() { + mixpanel.identify(distinctId: "d1") + let p: Properties = ["p1": "a"] + mixpanel.people.setOnce(properties: p) + waitForTrackingQueue() + let q = mixpanel.people.peopleQueue.last!["$set_once"] as! InternalProperties + XCTAssertEqual(q["p1"] as? String, "a", "custom people property not queued") + assertDefaultPeopleProperties(q) + } + + func testPeopleSetReservedProperty() { + mixpanel.identify(distinctId: "d1") + let p: Properties = ["$ios_app_version": "override"] + mixpanel.people.set(properties: p) + waitForTrackingQueue() + let q = mixpanel.people.peopleQueue.last!["$set"] as! InternalProperties + XCTAssertEqual(q["$ios_app_version"] as? String, + "override", + "reserved property override failed") + assertDefaultPeopleProperties(q) + } + + func testPeopleSetTo() { + mixpanel.identify(distinctId: "d1") + mixpanel.people.set(property: "p1", to: "a") + waitForTrackingQueue() + let p: InternalProperties = mixpanel.people.peopleQueue.last!["$set"] as! InternalProperties + XCTAssertEqual(p["p1"] as? String, "a", "custom people property not queued") + assertDefaultPeopleProperties(p) + } + + func testDropUnidentifiedPeopleRecords() { + QueueConstants.queueSize = 500 + for i in 0..<505 { + mixpanel.people.set(property: "i", to: i) + } + waitForTrackingQueue() + XCTAssertTrue(mixpanel.people.unidentifiedQueue.count == 500) + var r: InternalProperties = mixpanel.people.unidentifiedQueue.first! + XCTAssertEqual((r["$set"] as? InternalProperties)?["i"] as? Int, 5) + r = mixpanel.people.unidentifiedQueue.last! + XCTAssertEqual((r["$set"] as? InternalProperties)?["i"] as? Int, 504) + } + + func testDropPeopleRecords() { + QueueConstants.queueSize = 500 + mixpanel.identify(distinctId: "d1") + for i in 0..<505 { + mixpanel.people.set(property: "i", to: i) + } + waitForTrackingQueue() + XCTAssertTrue(mixpanel.people.peopleQueue.count == 500) + var r: InternalProperties = mixpanel.people.peopleQueue.first! + XCTAssertEqual((r["$set"] as? InternalProperties)?["i"] as? Int, 5) + r = mixpanel.people.peopleQueue.last! + XCTAssertEqual((r["$set"] as? InternalProperties)?["i"] as? Int, 504) + } + + func testPeopleAssertPropertyTypes() { + var p: Properties = ["URL": [Data()]] + XCTExpectAssert("unsupported property type was allowed") { + mixpanel.people.set(properties: p) + } + XCTExpectAssert("unsupported property type was allowed") { + mixpanel.people.set(property: "p1", to: [Data()]) + } + p = ["p1": "a"] + // increment should require a number + XCTExpectAssert("unsupported property type was allowed") { + mixpanel.people.increment(properties: p) + } + } + + func testPeopleAddPushDeviceToken() { + mixpanel.identify(distinctId: "d1") + let token: Data = "0123456789abcdef".data(using: String.Encoding.utf8)! + mixpanel.people.addPushDeviceToken(token) + waitForTrackingQueue() + XCTAssertTrue(mixpanel.people.peopleQueue.count == 1, "people records not queued") + let p: Properties = mixpanel.people.peopleQueue.last!["$union"] as! Properties + XCTAssertTrue(p.count == 1, "incorrect people properties: \(p)") + let a: [MixpanelType] = p["$ios_devices"] as! [MixpanelType] + XCTAssertTrue(a.count == 1, "device token array not set") + XCTAssertEqual(a.last as? String, + "30313233343536373839616263646566", + "device token not encoded properly") + } + + func testPeopleIncrement() { + mixpanel.identify(distinctId: "d1") + let p: Properties = ["p1": 3] + mixpanel.people.increment(properties: p) + waitForTrackingQueue() + let q = mixpanel.people.peopleQueue.last!["$add"] as! InternalProperties + XCTAssertTrue(q.count == 1, "incorrect people properties: \(p)") + XCTAssertEqual(q["p1"] as? Int, 3, "custom people property not queued") + } + + func testPeopleIncrementBy() { + mixpanel.identify(distinctId: "d1") + mixpanel.people.increment(property: "p1", by: 3) + waitForTrackingQueue() + let p: InternalProperties = mixpanel.people.peopleQueue.last!["$add"] as! InternalProperties + XCTAssertTrue(p.count == 1, "incorrect people properties: \(p)") + XCTAssertEqual(p["p1"] as? Double, 3, "custom people property not queued") + } + + func testPeopleDeleteUser() { + mixpanel.identify(distinctId: "d1") + mixpanel.people.deleteUser() + waitForTrackingQueue() + let p: InternalProperties = mixpanel.people.peopleQueue.last!["$delete"] as! InternalProperties + XCTAssertTrue(p.isEmpty, "incorrect people properties: \(p)") + } + + + func testPeopleTrackChargeDecimal() { + mixpanel.identify(distinctId: "d1") + mixpanel.people.trackCharge(amount: 25.34) + waitForTrackingQueue() + let r: InternalProperties = mixpanel.people.peopleQueue.last! + let prop = ((r["$append"] as? InternalProperties)?["$transactions"] as? InternalProperties)?["$amount"] as? Double + let prop2 = ((r["$append"] as? InternalProperties)?["$transactions"] as? InternalProperties)?["$time"] + XCTAssertEqual(prop, 25.34) + XCTAssertNotNil(prop2) + } + + func testPeopleTrackChargeZero() { + mixpanel.identify(distinctId: "d1") + mixpanel.people.trackCharge(amount: 0) + waitForTrackingQueue() + let r: InternalProperties = mixpanel.people.peopleQueue.last! + let prop = ((r["$append"] as? InternalProperties)?["$transactions"] as? InternalProperties)?["$amount"] as? Double + let prop2 = ((r["$append"] as? InternalProperties)?["$transactions"] as? InternalProperties)?["$time"] + XCTAssertEqual(prop, 0) + XCTAssertNotNil(prop2) + } + func testPeopleTrackChargeWithTime() { + mixpanel.identify(distinctId: "d1") + let p: Properties = allPropertyTypes() + mixpanel.people.trackCharge(amount: 25, properties: ["$time": p["date"]!]) + waitForTrackingQueue() + let r: InternalProperties = mixpanel.people.peopleQueue.last! + let prop = ((r["$append"] as? InternalProperties)?["$transactions"] as? InternalProperties)?["$amount"] as? Double + let prop2 = ((r["$append"] as? InternalProperties)?["$transactions"] as? InternalProperties)?["$time"] + XCTAssertEqual(prop, 25) + XCTAssertEqual(prop2 as? Date, p["date"] as? Date) + } + + func testPeopleTrackChargeWithProperties() { + mixpanel.identify(distinctId: "d1") + mixpanel.people.trackCharge(amount: 25, properties: ["p1": "a"]) + waitForTrackingQueue() + let r: InternalProperties = mixpanel.people.peopleQueue.last! + let prop = ((r["$append"] as? InternalProperties)?["$transactions"] as? InternalProperties)?["$amount"] as? Double + let prop2 = ((r["$append"] as? InternalProperties)?["$transactions"] as? InternalProperties)?["p1"] + XCTAssertEqual(prop, 25) + XCTAssertEqual(prop2 as? String, "a") + } + + func testPeopleTrackCharge() { + mixpanel.identify(distinctId: "d1") + mixpanel.people.trackCharge(amount: 25) + waitForTrackingQueue() + let r: InternalProperties = mixpanel.people.peopleQueue.last! + let prop = ((r["$append"] as? InternalProperties)?["$transactions"] as? InternalProperties)?["$amount"] as? Double + let prop2 = ((r["$append"] as? InternalProperties)?["$transactions"] as? InternalProperties)?["$time"] + XCTAssertEqual(prop, 25) + XCTAssertNotNil(prop2) + } + + func testPeopleClearCharges() { + mixpanel.identify(distinctId: "d1") + mixpanel.people.clearCharges() + waitForTrackingQueue() + let r: InternalProperties = mixpanel.people.peopleQueue.last! + let transactions = (r["$set"] as? InternalProperties)?["$transactions"] as? [MixpanelType] + XCTAssertEqual(transactions?.count, 0) + } +} diff --git a/MixpanelDemo/MixpanelDemoMacTests/TestConstants.swift b/MixpanelDemo/MixpanelDemoMacTests/TestConstants.swift new file mode 100644 index 000000000..fec5df68c --- /dev/null +++ b/MixpanelDemo/MixpanelDemoMacTests/TestConstants.swift @@ -0,0 +1,55 @@ +// +// TestConstants.swift +// MixpanelDemo +// +// Created by Yarden Eitan on 6/28/16. +// Copyright © 2016 Mixpanel. All rights reserved. +// + +import Nocilla +import XCTest + +@testable import Mixpanel + +let kTestToken = "abc123" +let kDefaultServerString = "https://api.mixpanel.com" +let kDefaultServerTrackString = "https://api.mixpanel.com/track/" +let kDefaultServerEngageString = "https://api.mixpanel.com/engage/" +let kDefaultServerGroupsString = "https://api.mixpanel.com/groups/" +let kDefaultServerDecideString = "^https://api.mixpanel.com/decide(.*?)".regex + +@discardableResult func stubEngage() -> LSStubRequestDSL { + return stubRequest("POST", kDefaultServerEngageString as LSMatcheable).withHeader("Accept-Encoding", "gzip")! +} + +@discardableResult func stubGroups() -> LSStubRequestDSL { + return stubRequest("POST", kDefaultServerGroupsString as LSMatcheable?).withHeader("Accept-Encoding", "gzip")! +} + +@discardableResult func stubTrack() -> LSStubRequestDSL { + return stubRequest("POST", kDefaultServerTrackString as LSMatcheable).withHeader("Accept-Encoding", "gzip")! +} + +@discardableResult func stubDecide() -> LSStubRequestDSL { + return stubRequest("GET", kDefaultServerDecideString()).withHeader("Accept-Encoding", "gzip")! +} + +extension XCTestCase { + + func XCTExpectAssert(_ expectedMessage: String, file: StaticString = #file, line: UInt = #line, block: () -> ()) { + let exp = expectation(description: expectedMessage) + + Assertions.assertClosure = { + (condition, message, file, line) in + if !condition { + exp.fulfill() + } + } + + // Call code. + block() + waitForExpectations(timeout: 0.5, handler: nil) + Assertions.assertClosure = Assertions.swiftAssertClosure + } + +} diff --git a/MixpanelDemo/MixpanelDemoMacUITests/Info.plist b/MixpanelDemo/MixpanelDemoMacUITests/Info.plist new file mode 100644 index 000000000..64d65ca49 --- /dev/null +++ b/MixpanelDemo/MixpanelDemoMacUITests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/MixpanelDemo/MixpanelDemoMacUITests/MixpanelDemoMacUITests.swift b/MixpanelDemo/MixpanelDemoMacUITests/MixpanelDemoMacUITests.swift new file mode 100644 index 000000000..40c07ab80 --- /dev/null +++ b/MixpanelDemo/MixpanelDemoMacUITests/MixpanelDemoMacUITests.swift @@ -0,0 +1,43 @@ +// +// MixpanelDemoMacUITests.swift +// MixpanelDemoMacUITests +// +// Created by ZIHE JIA on 6/7/21. +// Copyright © 2021 Mixpanel. All rights reserved. +// + +import XCTest + +class MixpanelDemoMacUITests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + + // In UI tests it is usually best to stop immediately when a failure occurs. + continueAfterFailure = false + + // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testExample() throws { + // UI tests must launch the application that they test. + let app = XCUIApplication() + app.launch() + + // Use recording to get started writing UI tests. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testLaunchPerformance() throws { + if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { + // This measures how long it takes to launch your application. + measure(metrics: [XCTApplicationLaunchMetric()]) { + XCUIApplication().launch() + } + } + } +} diff --git a/MixpanelDemo/MixpanelDemoTests/MixpanelOptOutTests.swift b/MixpanelDemo/MixpanelDemoTests/MixpanelOptOutTests.swift index b11ebaf53..c0b3609ef 100644 --- a/MixpanelDemo/MixpanelDemoTests/MixpanelOptOutTests.swift +++ b/MixpanelDemo/MixpanelDemoTests/MixpanelOptOutTests.swift @@ -192,19 +192,6 @@ class MixpanelOptOutTests: MixpanelBaseTests { XCTAssertTrue(mixpanel.people.peopleQueue.count == 0, "When opted out, events should not be queued") } - func testOptOutTrackingWillSkipIdentify() - { - mixpanel.optOutTracking() - mixpanel.identify(distinctId: "d1") - //opt in again just to enable people queue - mixpanel.optInTracking() - for i in 0..<50 { - mixpanel.people.set(property: "p1", to: "\(i)") - } - waitForMixpanelQueues() - XCTAssertTrue(mixpanel.people.unidentifiedQueue.count == 50, "When opted out, calling identify should be skipped") - } - func testOptOutTrackingWillSkipAlias() { mixpanel.optOutTracking() @@ -212,6 +199,17 @@ class MixpanelOptOutTests: MixpanelBaseTests { XCTAssertNotEqual(mixpanel.alias, "testAlias", "When opted out, alias should not be set") } + func testEventBeingTrackedBeforeOptOutShouldNotBeCleared() + { + mixpanel = Mixpanel.initialize(token: randomId()) + mixpanel.track(event: "a normal event") + waitForMixpanelQueues() + XCTAssertTrue(mixpanel.eventsQueue.count == 1, "events should be queued") + mixpanel.optOutTracking() + waitForMixpanelQueues() + XCTAssertTrue(mixpanel.eventsQueue.count == 1, "When opted out, any events tracked before opted out should not be cleared") + } + func testOptOutTrackingRegisterSuperProperties() { let properties: Properties = ["p1": "a", "p2": 3, "p3": Date()] diff --git a/MixpanelDemo/Podfile b/MixpanelDemo/Podfile index f38abcf60..655b75d06 100644 --- a/MixpanelDemo/Podfile +++ b/MixpanelDemo/Podfile @@ -3,3 +3,7 @@ use_frameworks! target 'MixpanelDemoTests' do pod 'Nocilla' end + +target 'MixpanelDemoMacTests' do + pod 'Nocilla' +end diff --git a/Sources/Mixpanel.swift b/Sources/Mixpanel.swift index 768d0a200..53d097bc3 100644 --- a/Sources/Mixpanel.swift +++ b/Sources/Mixpanel.swift @@ -76,7 +76,8 @@ open class Mixpanel { optOutTrackingByDefault: Bool = false) -> MixpanelInstance { return MixpanelManager.sharedInstance.initialize(token: apiToken, flushInterval: flushInterval, - instanceName: instanceName) + instanceName: instanceName, + optOutTrackingByDefault: optOutTrackingByDefault) } #endif // os(OSX) diff --git a/Sources/MixpanelInstance.swift b/Sources/MixpanelInstance.swift index 8496b7458..2e02bfff9 100644 --- a/Sources/MixpanelInstance.swift +++ b/Sources/MixpanelInstance.swift @@ -1054,7 +1054,8 @@ extension MixpanelInstance { alias, hadPersistedDistinctId, people.distinctId, - people.unidentifiedQueue) = Persistence.unarchive(token: apiToken) + people.unidentifiedQueue, + optOutStatus) = Persistence.unarchive(token: apiToken) if distinctId == "" { distinctId = defaultDistinctId() @@ -1657,16 +1658,7 @@ extension MixpanelInstance { This method is used to opt out tracking. This causes all events and people request no longer to be sent back to the Mixpanel server. */ - open func optOutTracking() { - trackingQueue.async { [weak self] in - guard let self = self else { return } - - self.readWriteLock.write { - self.eventsQueue = Queue() - self.people.peopleQueue = Queue() - } - } - + open func optOutTracking() { if people.distinctId != nil { people.deleteUser() people.clearCharges() @@ -1680,7 +1672,6 @@ extension MixpanelInstance { return } self.readWriteLock.write { [weak self] in - guard let self = self else { return } diff --git a/Sources/Persistence.swift b/Sources/Persistence.swift index 4ffd36d45..bcb181144 100644 --- a/Sources/Persistence.swift +++ b/Sources/Persistence.swift @@ -255,10 +255,12 @@ class Persistence { alias: String?, hadPersistedDistinctId: Bool?, peopleDistinctId: String?, - peopleUnidentifiedQueue: Queue) { + peopleUnidentifiedQueue: Queue, + optOutStatus: Bool?) { let eventsQueue = unarchiveEvents(token: token) let peopleQueue = unarchivePeople(token: token) let groupsQueue = unarchiveGroups(token: token) + let optOutStatus = unarchiveOptOutStatus(token: token) let (superProperties, timedEvents, @@ -282,7 +284,8 @@ class Persistence { alias, hadPersistedDistinctId, peopleDistinctId, - peopleUnidentifiedQueue) + peopleUnidentifiedQueue, + optOutStatus) } #endif // DECIDE From 62d08b3608b403bfa9a22b2440e3c548702be862 Mon Sep 17 00:00:00 2001 From: Zihe Jia Date: Thu, 10 Jun 2021 10:43:10 -0700 Subject: [PATCH 2/2] fix the github flow --- .github/workflows/{swift.yml => iOS.yml} | 3 +- .github/workflows/macOS.yml | 30 +++++++++++++++++++ .../MixpanelDemo.xcodeproj/project.pbxproj | 12 ++++---- .../xcschemes/MixpanelDemoMac.xcscheme | 2 +- .../MixpanelDemoMac/AppDelegate.swift | 3 +- .../MixpanelOptOutTests.swift | 17 ++++++++++- .../MixpanelOptOutTests.swift | 15 ++++++++++ 7 files changed, 71 insertions(+), 11 deletions(-) rename .github/workflows/{swift.yml => iOS.yml} (72%) create mode 100644 .github/workflows/macOS.yml diff --git a/.github/workflows/swift.yml b/.github/workflows/iOS.yml similarity index 72% rename from .github/workflows/swift.yml rename to .github/workflows/iOS.yml index 9c69a4087..9b0db427a 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/iOS.yml @@ -12,7 +12,6 @@ jobs: strategy: matrix: destination: ['OS=14.4,name="iPhone 11"'] - scheme: ['MixpanelDemo', 'MixpanelDemoMac'] steps: - uses: actions/checkout@v2 @@ -26,7 +25,7 @@ jobs: working-directory: MixpanelDemo run: | set -o pipefail - xcodebuild -workspace MixpanelDemo.xcworkspace -scheme ${{ matrix.scheme }} -derivedDataPath Build/ -destination ${{ matrix.destination }} -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES -enableCodeCoverage YES clean build test | xcpretty -c; + xcodebuild -workspace MixpanelDemo.xcworkspace -scheme MixpanelDemo -derivedDataPath Build/ -destination ${{ matrix.destination }} -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES -enableCodeCoverage YES clean build test | xcpretty -c; - name: Pod Lint run: pod lib lint --allow-warnings - name: Code Coverage Report diff --git a/.github/workflows/macOS.yml b/.github/workflows/macOS.yml new file mode 100644 index 000000000..f8371919c --- /dev/null +++ b/.github/workflows/macOS.yml @@ -0,0 +1,30 @@ +name: Mixpanel Swift SDK action + +on: + push: + branches: [ master, 3.0.0.beta ] + pull_request: + branches: [ master, 3.0.0.beta ] + +jobs: + build: + runs-on: macos-latest + + steps: + - uses: actions/checkout@v2 + - name: Install Dependencies + working-directory: MixpanelDemo + run: + pod install --repo-update + - name: Swift Lint + run: swiftlint + - name: Run Test + working-directory: MixpanelDemo + run: | + set -o pipefail + xcodebuild -workspace MixpanelDemo.xcworkspace -scheme MixpanelDemoMac -derivedDataPath Build/ -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES -enableCodeCoverage YES clean build test | xcpretty -c; + - name: Code Coverage Report + working-directory: MixpanelDemo/build/Logs/Test + run: | + xcrun xccov view --report --files-for-target Mixpanel.framework *.xcresult + xcrun xccov view --report --only-targets *.xcresult diff --git a/MixpanelDemo/MixpanelDemo.xcodeproj/project.pbxproj b/MixpanelDemo/MixpanelDemo.xcodeproj/project.pbxproj index b4a88007b..7cbde1223 100644 --- a/MixpanelDemo/MixpanelDemo.xcodeproj/project.pbxproj +++ b/MixpanelDemo/MixpanelDemo.xcodeproj/project.pbxproj @@ -1519,7 +1519,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.2; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.mixpanel.MixpanelDemoMac; @@ -1549,7 +1549,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.2; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.mixpanel.MixpanelDemoMac; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1581,7 +1581,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.2; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.mixpanel.MixpanelDemoMacTests; @@ -1616,7 +1616,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.2; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.mixpanel.MixpanelDemoMacTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1647,7 +1647,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.2; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.mixpanel.MixpanelDemoMacUITests; @@ -1677,7 +1677,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.2; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.mixpanel.MixpanelDemoMacUITests; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemoMac.xcscheme b/MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemoMac.xcscheme index 60d7b360a..5215638e9 100644 --- a/MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemoMac.xcscheme +++ b/MixpanelDemo/MixpanelDemo.xcodeproj/xcshareddata/xcschemes/MixpanelDemoMac.xcscheme @@ -27,7 +27,7 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES" - enableThreadSanitizer = "YES"> + disableMainThreadChecker = "YES"> diff --git a/MixpanelDemo/MixpanelDemoMac/AppDelegate.swift b/MixpanelDemo/MixpanelDemoMac/AppDelegate.swift index 35331d2a5..ae033a944 100644 --- a/MixpanelDemo/MixpanelDemoMac/AppDelegate.swift +++ b/MixpanelDemo/MixpanelDemoMac/AppDelegate.swift @@ -22,7 +22,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { Mixpanel.initialize(token: "MIXPANEL_TOKEN") Mixpanel.mainInstance().loggingEnabled = true - Mixpanel.mainInstance().track("Tracked Event") + Mixpanel.mainInstance().track(event: "Tracked Event") + } func applicationWillTerminate(_ aNotification: Notification) { diff --git a/MixpanelDemo/MixpanelDemoMacTests/MixpanelOptOutTests.swift b/MixpanelDemo/MixpanelDemoMacTests/MixpanelOptOutTests.swift index d04d37f02..f4c2837d9 100644 --- a/MixpanelDemo/MixpanelDemoMacTests/MixpanelOptOutTests.swift +++ b/MixpanelDemo/MixpanelDemoMacTests/MixpanelOptOutTests.swift @@ -19,6 +19,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptInWillAddOptInEvent() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optInTracking() XCTAssertFalse(mixpanel.hasOptedOutTracking(), "The current user should have opted in tracking") waitForMixpanelQueues() @@ -35,7 +36,8 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptInTrackingForDistinctId() { - mixpanel .optInTracking(distinctId: "testDistinctId") + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) + mixpanel.optInTracking(distinctId: "testDistinctId") XCTAssertFalse(mixpanel.hasOptedOutTracking(), "The current user should have opted in tracking") waitForTrackingQueue() if mixpanel.eventsQueue.count > 0 { @@ -58,6 +60,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { "number": 3, "date": now, "$app_version": "override"] + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optInTracking(distinctId: "testDistinctId", properties: testProperties) waitForTrackingQueue() let props = mixpanel.eventsQueue.first!["properties"] as? InternalProperties @@ -105,12 +108,14 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testHasOptOutTrackingFlagBeingSetProperlyForOptOut() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optOutTracking() XCTAssertTrue(mixpanel.hasOptedOutTracking(), "When optOutTracking is called, the current user should have opted out tracking") } func testHasOptOutTrackingFlagBeingSetProperlyForOptIn() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optOutTracking() XCTAssertTrue(mixpanel.hasOptedOutTracking(), "When optOutTracking is called, the current user should have opted out tracking") mixpanel.optInTracking() @@ -130,6 +135,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutTrackingWillNotGenerateEventQueue() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optOutTracking() for i in 0..<50 { mixpanel.track(event: "event \(i)") @@ -140,6 +146,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutTrackingWillNotGeneratePeopleQueue() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optOutTracking() for i in 0..<50 { mixpanel.people.set(property: "p1", to: "\(i)") @@ -150,6 +157,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutTrackingWillSkipAlias() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optOutTracking() mixpanel.createAlias("testAlias", distinctId: "aDistinctId") XCTAssertNotEqual(mixpanel.alias, "testAlias", "When opted out, alias should not be set") @@ -157,6 +165,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutTrackingRegisterSuperProperties() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) let properties: Properties = ["p1": "a", "p2": 3, "p3": Date()] mixpanel.optOutTracking() mixpanel.registerSuperProperties(properties) @@ -168,6 +177,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutTrackingRegisterSuperPropertiesOnce() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) let properties: Properties = ["p1": "a", "p2": 3, "p3": Date()] mixpanel.optOutTracking() mixpanel.registerSuperPropertiesOnce(properties) @@ -179,6 +189,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutWilSkipTimeEvent() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optOutTracking() mixpanel.time(event: "400 Meters") mixpanel.track(event: "400 Meters") @@ -188,6 +199,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutTrackingWillPurgeEventQueue() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optInTracking() mixpanel.identify(distinctId: "d1") for i in 0..<50 { @@ -204,6 +216,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutTrackingWillPurgePeopleQueue() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optInTracking() mixpanel.identify(distinctId: "d1") for i in 0..<50 { @@ -219,6 +232,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutWillSkipFlushPeople() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optInTracking() mixpanel.identify(distinctId: "d1") for i in 0..<50 { @@ -239,6 +253,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutWillSkipFlushEvent() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optInTracking() mixpanel.identify(distinctId: "d1") for i in 0..<50 { diff --git a/MixpanelDemo/MixpanelDemoTests/MixpanelOptOutTests.swift b/MixpanelDemo/MixpanelDemoTests/MixpanelOptOutTests.swift index c0b3609ef..bba186a61 100644 --- a/MixpanelDemo/MixpanelDemoTests/MixpanelOptOutTests.swift +++ b/MixpanelDemo/MixpanelDemoTests/MixpanelOptOutTests.swift @@ -74,6 +74,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptInWillAddOptInEvent() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optInTracking() XCTAssertFalse(mixpanel.hasOptedOutTracking(), "The current user should have opted in tracking") waitForMixpanelQueues() @@ -90,6 +91,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptInTrackingForDistinctId() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel .optInTracking(distinctId: "testDistinctId") XCTAssertFalse(mixpanel.hasOptedOutTracking(), "The current user should have opted in tracking") waitForTrackingQueue() @@ -160,12 +162,14 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testHasOptOutTrackingFlagBeingSetProperlyForOptOut() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optOutTracking() XCTAssertTrue(mixpanel.hasOptedOutTracking(), "When optOutTracking is called, the current user should have opted out tracking") } func testHasOptOutTrackingFlagBeingSetProperlyForOptIn() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optOutTracking() XCTAssertTrue(mixpanel.hasOptedOutTracking(), "When optOutTracking is called, the current user should have opted out tracking") mixpanel.optInTracking() @@ -174,6 +178,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutTrackingWillNotGenerateEventQueue() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optOutTracking() for i in 0..<50 { mixpanel.track(event: "event \(i)") @@ -184,6 +189,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutTrackingWillNotGeneratePeopleQueue() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optOutTracking() for i in 0..<50 { mixpanel.people.set(property: "p1", to: "\(i)") @@ -194,6 +200,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutTrackingWillSkipAlias() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optOutTracking() mixpanel.createAlias("testAlias", distinctId: "aDistinctId") XCTAssertNotEqual(mixpanel.alias, "testAlias", "When opted out, alias should not be set") @@ -201,6 +208,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testEventBeingTrackedBeforeOptOutShouldNotBeCleared() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel = Mixpanel.initialize(token: randomId()) mixpanel.track(event: "a normal event") waitForMixpanelQueues() @@ -212,6 +220,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutTrackingRegisterSuperProperties() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) let properties: Properties = ["p1": "a", "p2": 3, "p3": Date()] mixpanel.optOutTracking() mixpanel.registerSuperProperties(properties) @@ -223,6 +232,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutTrackingRegisterSuperPropertiesOnce() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) let properties: Properties = ["p1": "a", "p2": 3, "p3": Date()] mixpanel.optOutTracking() mixpanel.registerSuperPropertiesOnce(properties) @@ -234,6 +244,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutWilSkipTimeEvent() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optOutTracking() mixpanel.time(event: "400 Meters") mixpanel.track(event: "400 Meters") @@ -243,6 +254,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutTrackingWillPurgeEventQueue() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optInTracking() mixpanel.identify(distinctId: "d1") for i in 0..<50 { @@ -259,6 +271,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutTrackingWillPurgePeopleQueue() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optInTracking() mixpanel.identify(distinctId: "d1") for i in 0..<50 { @@ -274,6 +287,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutWillSkipFlushPeople() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optInTracking() mixpanel.identify(distinctId: "d1") for i in 0..<50 { @@ -294,6 +308,7 @@ class MixpanelOptOutTests: MixpanelBaseTests { func testOptOutWillSkipFlushEvent() { + mixpanel = Mixpanel.initialize(token: randomId(), optOutTrackingByDefault: true) mixpanel.optInTracking() mixpanel.identify(distinctId: "d1") for i in 0..<50 {